前言
最近几周参加笔试面试,总是会遇到实现异步和处理异步的问题,然而作者每次都无法完美地回答。在最近一次笔试因为 Promise
而被刷掉后,我终于下定决心一个个地搞懂它们,就先拿 Promise
开刀吧 :)。
用法解析
ES6 的Promise
对象是一个代理对象,被代理的值在Promise
对象创建时可能是未知的,另外它允许你为异步操作的成功和失败分别绑定相应的处理方法。Promise
常用于控制异步操作的执行顺序,而且可以让异步方法像同步方法那样返回值。它不能立即取得异步方法的返回值,但是它可以代理这个值,一旦异步操作完成,就会以及将值传递给相应的处理方法。
一个Promise
对象有以下几种状态:
pending
: 初始状态,既不是成功,也不是失败状态。fulfilled
: 意味着操作成功完成。rejected
: 意味着操作失败。
一个Promise
对象的状态可以从pending
变成fulfilled
,同时传递一个值给相应的onfulfilled
处理方法;也可以从pending
变成rejected
,同时传递一个失败信息给相应的onrejected
处理方法。
一旦一个Promise
对象的状态发生改变,就会触发之前通过Promise.prototype.then
、 Promise.prototype.catch
和 Promise.prototype.finally
方法绑定的onfulfilled
、onrejected
和onFinally
处理方法。
因为then
、catch
和finally
方法都会返回一个新的Promise
对象, 所以它们可以被链式调用。
构造函数
构造函数Promise()
主要用来包装还未支持 promises 的函数。
new Promise( function(resolve, reject) {...} /* executor */ );
参数:executor
executor
是带有 resolve
和 reject
两个函数参数的函数。Promise
构造函数执行时立即调用executor
函数,换句话说,executor
函数是在Promise
构造函数内执行的,所以它是同步代码。在executor
函数内调用 resolve
和 reject
函数,可以传递参数给相应的处理方法,并会分别将 promise
新建对象的状态改为fulfilled
(完成)或rejected
(失败)。executor
内部通常会执行一些异步操作如ajax
,一旦完成,可以调用resolve
函数传递参数并将 promise 对象的状态改成 fulfilled,或者在发生错误时调用reject
函数传递参数并将 promise 对象的状态改成 rejected。如下:
function myAsyncFunction(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open("GET", url);
xhr.onload = () => resolve(xhr.responseText);
xhr.onerror = () => reject(xhr.statusText);
xhr.send();
});
};
如果在executor
函数中抛出一个同步错误,那么该 promise 对象的状态将会变为rejected
,并将错误作为参数传递给对应的onrejected
处理方法。如下:
new Promise((_, reject) => {
throw 'a'; // 等同于 reject('a');
}).catch(e => {
console.log(e); // 'a'
});
静态方法
静态方法是定义在构造函数上的方法,声明静态方法:
Func.fn = function() {}
调用静态方法:
Func.fn(args);
ES6
中的Promise
构造函数有4个静态方法:
- Promise.
resolve
(value) - Promise.
reject
(reason) - Promise.
all
(iterable) - Promise.
race
(iterable)
Promise.resolve
(value):
返回一个由参数value
解析而来的Promise
对象。 - 如果传入的
value
本身就是Promise
对象,则直接返回value
; - 如果
value
是一个thenable
对象(带有 then 方法的对象),返回的Promise
对象会跟随这个thenable
对象,状态随之变化; - 其它情况下返回的
Promise
对象状态为fulfilled
,并且将该value
作为参数传递给onfulfilled
处理方法。
通常而言,如果你不知道一个值是否为Promise
对象,就可以使用 Promise.resolve(value) 来将value
以Promise
对象的形式使用。
// resolve一个thenable对象
var p1 = Promise.resolve({
then: function(onFulfill, onReject) { onFulfill("fulfilled!"); }
});
console.log(p1 instanceof Promise) // true, 这是一个Promise对象
p1.then(function(v) {
console.log(v); // 输出"fulfilled!"
}, function(e) {
// 不会被调用
});
Promise.reject
(reason):
返回一个被给定原因reason
拒绝的Promise
对象。
返回的Promise
对象的status
状态属性为rejected
,reason
拒绝原因属性(传递给onrejected
处理方法的 reason 参数)与参数reason
相等。
Promise.reject(new Promise((resolve, reject) => resolve('done')))
.then(function(reason) {
// 未被调用
}, function(reason) {
console.log(reason); // new Promise
});
Promise.all
(iterable):
参数:iterable
对象为Array
对象、Map
对象和Set
对象等可迭代对象。
返回:一个Promise
对象。
- 如果
iterable
不是一个可迭代对象,Promise.all
会同步地返回返回一个状态为rejected
,拒绝原因reson
为类型错误的 promise 对象; - 如果
iterable
对象为空,Promise.all
会同步地返回一个状态为fulfilled
,value
值为空数组的 promise 对象; - 如果
iterable
对象中的 promise 对象都变为fulfilled
状态,或者iterable
对象内没有 promise 对象,Promise.all
返回的 promise 对象将异步地变为fulfilled
状态。这两种情况返回的 promise 对象的value
值(传递给onfulfilled
处理方法的 value 参数)都是一个数组,这个数组包含iterable
对象中所有的基本值和 promise 对象value
值,这些值将会按照参数iterable
内的顺序排列,而不是由 promise 的完成顺序决定; 如果
iterable
对象中任意一个 promise 对象状态变为rejected
,那么Promise.all
就会异步地返回一个状态为rejected
的 promise 对象,而且此 promise 对象的reason
值(传递给onrejected
处理方法的 reason 参数),等于iterable
对象中状态为rejected
的那一个 promise 对象的reason
值。// this will be counted as if the iterable passed is empty, so it gets fulfilled var p = Promise.all([1,2,3]); // this will be counted as if the iterable passed contains only the resolved promise with value "333", so it gets fulfilled var p2 = Promise.all([1,2,3, Promise.resolve(333)]); // this will be counted as if the iterable passed contains the rejected promise with value "444", so it gets rejected var p3 = Promise.all([1,2, Promise.reject(444), Promise.reject(555)]); // using setTimeout we can execute code after the stack is empty setTimeout(function(){ console.log(p); console.log(p2); console.log(p3); }); // logs // Promise { <state>: "fulfilled", <value>: Array[3] } // Promise { <state>: "fulfilled", <value>: Array[4] } // Promise { <state>: "rejected", <reason>: 444 }
Promise.
race
(iterable):
返回一个Promise
对象。- 如果
iterable
不是一个可迭代对象,Promise.race
会同步地返回返回一个状态为rejected
,拒绝原因reson
为类型错误的 promise 对象; - 如果
iterable
对象为空,Promise.all
会同步地返回一个状态为fulfilled
,value
值为空数组的 promise 对象; 如果
iterable
对象不为空,一旦iterable
中的某个 promise 对象完成或拒绝,返回的 promise 对象就会完成或拒绝,且返回的 promise 对象的value
值(完成时)或reason
值(拒绝时)和这个 promise 对象的value
值(完成时)或reason
值(拒绝时)相等。var promise1 = new Promise(function(resolve, reject) { setTimeout(resolve, 500, 'one'); }), promise2 = new Promise(function(resolve, reject) { setTimeout(resolve, 100, 'two'); }); Promise.race([promise1, promise2]).then(function(value) { console.log(value); // Both resolve, but promise2 is faster }); // expected output: "two"
实例方法
实例方法是定义在原型对象上的方法,声明实例方法:
Func.prototype.fn = function() {}
调用实例方法需要先创建一个实例:
let func = new Func();
func.fn(args);
Promise
的原型对象上有3个实例方法:
- Promise.prototype.
then
(onFulfilled
,onRejected
) - Promise.prototype.
catch
(onRejected
) - Promise.prototype.
finally
(onFinally
)
Promise.prototype.then
(onFulfilled
, onRejected
):then
方法接收成功和失败两种情况的回调函数作为参数,返回一个Promise
对象。
参数:onFulfilled
和onRejected
回调函数。onFulfilled
:当 promise 对象变成 fulfilled 状态时被调用。onFulfilled
函数有一个参数,即 promise 对象完成时的 value
值。如果onFulfilled
不是函数,它会在then
方法内部被替换成一个Identity
函数,即 (x) => (x) 。onRejected
:当 promise 对象变成 rejected 状态时被调用。onRejected
函数有一个参数,即 promise 对象失败时的 reason
值。如果onRejected
不是函数,它会在then
方法内部被替换成一个Thrower
函数,即 (reason) => {throw reason} 。
返回:一旦调用then
方法的 promise 对象被完成或拒绝,将异步调用相应的处理函数(onFulfilled
或onRejected
),即将处理函数加入microtask
队列中。如果onFulfilled
或onRejected
回调函数:
- 返回一个值,则
then
返回的 promise 对象的status
变为fulfilled
,value
变为回调函数的返回值; - 不返回任何内容,则
then
返回的 promise 对象的status
变为fulfilled
,value
变为undefined
; - 抛出一个错误,则
then
返回的 promise 对象的status
变为rejected
,reason
变为回调函数抛出的错误; - 返回一个状态为
fulfilled
的 promise 对象,则then
返回的 promise 对象的status
变为fulfilled
,value
等于回调函数返回的 promise 对象的value
值; - 返回一个状态为
rejected
的 promise 对象,则then
返回的 promise 对象的status
变为rejected
,reason
等于回调函数返回的 promise 对象的reason
值; - 返回一个状态为
pending
的 promise 对象,则then
返回的 promise 对象的status
变为pending
,且其status
将随着回调函数返回的 promise 对象的status
变化而变化,之后其value
或reason
值也会和此 promise 对象的value
或reason
值相同。
这里提一下,这个地方看 MDN 文档中文翻译实在看不懂,之后看英文原文反而稍微理解了,希望之后在实现 Promise 的过程中能理解更深。
var fromCallback;
var fromThen = Promise.resolve('done')
.then(function() {
fromCallback = new Promise(function(){});
return fromCallback;
});
setTimeout(function() {
console.log(fromCallback); //fromCallback.status === 'pending'
console.log(fromThen); //fromThen.status === 'pending'
console.log(fromCallback === fromThen); //false
}, 0);
Promise.prototype.catch
(onRejected
):catch
方法接收失败情况的回调函数作为参数,返回一个Promise
对象。
参数:onRejected
回调函数,表现同then
中的onRejected
参数。
返回:promiseObj.catch(onRejected) 与 promiseObj.then(undefined, onRejected) 返回值相同。
Promise.resolve()
.then( () => {
// 返回一个 rejected promise
throw 'Oh no!';
})
.catch( reason => {
console.error( 'onRejected function called: ', reason );
})
.then( () => {
console.log( "I am always called even if the prior then's promise rejects" );
});
Promise.prototype.finally
(onFinally
):finally
方法接收onFinally
回调函数作为参数,返回一个Promise
对象。
如果你想在 promise 执行完毕后,无论其结果是成功还是失败,都做一些相同的处理时,可以使用finally
方法。
参数:onFinally
回调函数onFinally
不接收任何参数,当 promise 对象变成 settled (fulfilled / rejected) 状态时onFinally
被调用。
返回:如果onFinally
回调函数
- 不返回任何内容或者返回一个值或者返回一个状态为
fulfilled
的 promise 对象,则finally
返回的 promise 对象的status
、value
和reason
值与调用这个finally
方法的 promise 对象的值相同; - 抛出一个错误或者返回一个状态为
rejected
的 promise 对象,则finally
返回的 promise 对象的status
值变为rejected
,reason
值变为被抛出的错误或者回调函数返回的 promise 对象的reason
值; - 返回一个状态为
pending
的 promise 对象,则finally
返回的 promise 对象的status
值变为pending
,且其status
值将随着回调函数返回的 promise 对象的status
值变化而变化,之后其value
或reason
值也会和此 promise 对象的value
或reason
值相同。
Promise.reject('是我,开心吗').finally(function() {
var pro = new Promise(function(r){r('你得不到我')}); //pro.status === 'fulfilled'
return pro; //`onFinally`回调函数返回一个状态为`fulfilled`的 promise 对象
}).catch(function(reason) {
console.log(reason);
});
结语
将 MDN 文档整理了一下,加入了一些自己的理解,也花费了一天的时间。现在Promise
各个方法的参数、返回值、功能和使用方法已经有个大概的了解了,为了进一步理解其原理,接下来我打算简单地实现一下它。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。