尝试使用 js 手写 Promise 类,实现部分功能,遵守 Promises/A+ 规范,例如 then, catch, all, race, resolve, reject 等。
使用原生 Promise 查看效果
let promise1 = new Promise((resolve, reject) => {
  resolve(1);
});
promise1
  .then(res => {
    console.log(res);
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve(2);
      }, 1000);
    });
  })
  .then(res => {
    console.log(res);
    throw new Error();
  })
  .catch(err => {
    console.log(err);
  });
// 1
// 1秒后
// 2
// Error
从上述代码中可以看出 promise 的几个性质,首先它是一个类,可以被实例化,参数为一个函数,有 resolve 和 reject 两个参数。当期望的事情完成后,调用 resolve 表明成功,调用 reject 表明失败。成功后可以调用 .then 来进行后续操作,默认参数为 resolve 函数的参数。返回值可以是一个新的 promise ,后续操作会等待这个 promise 的完成。抛出错误时或是手动调用 reject 都会进入 catch 回调,或是 .then 方法的第二个参数。
接下来,我们一步步实现。
实现过程
定义
promise构造函数首先定义三个常量用于表示状态,
Promises/A+规范中提到:A promise must be in one of three states: pending, fulfilled, or rejected.
When pending, a promise:
- may transition to either the fulfilled or rejected state.
 
When fulfilled, a promise:
- must not transition to any other state.
 
must have a value, which must not change.
When rejected, a promise:
- must not transition to any other state.
 
must have a reason, which must not change.
Here, “must not change” means immutable identity (i.e.
===), but does not imply deep immutability.promise仅有三种状态,表示还未完成,已成功或是已失败。当处于pending状态时,可能会转换为另外两种状态。当处于fulfilled状态时,不会再转为其余状态,必须拥有一个value属性且不再变化。当处于rejected状态时,同样不会再转变,且必须拥有一个reason属性不再变化。const PENDING = 'pending', FULFILLED = 'fulfilled', REJECTED = 'rejected';
class MyPromise {
     constructor(executor) {
       this.status = PENDING;
       this.value = undefined;
       this.reason = undefined;
   const resolve = () => {};
   const reject = () => {};
   executor(resolve, reject);
 }}
   所以在构造函数中,我们定义三个变量,初始化此时的状态为 `pending` ,并且传入的 `executor` 函数我们需要进行调用,传入定义的两个函数作为参数。
   然后定义 `resolve` 和 `reject` 的内容。
   ```js
const resolve = value => {
     if (this.status !== PENDING) return;
  this.status === FULFILLED;
     this.value = value;
};
   const reject = reason => {
     if (this.status !== PENDING) return;
     this.status === REJECTED;
     this.reason = reason;
   };   遵循规范,若当前状态不是 pending ,不执行后续操作。
定义
then方法首先查看规范:
A promise’s
thenmethod accepts two arguments:promise.then(onFulfilled, onRejected)Both
onFulfilledandonRejectedare optional arguments:- If 
onFulfilledis not a function, it must be ignored. - If 
onRejectedis not a function, it must be ignored. 
- If 
 If
onFulfilledis a function:- it must be called after 
promiseis fulfilled, withpromise’s value as its first argument. - it must not be called before 
promiseis fulfilled. - it must not be called more than once.
 
- it must be called after 
 If
onRejectedis a function,- it must be called after 
promiseis rejected, withpromise’s reason as its first argument. - it must not be called before 
promiseis rejected. - it must not be called more than once.
 
- it must be called after 
 onFulfilledoronRejectedmust not be called until the execution context stack contains only platform code. [3.1].onFulfilledandonRejectedmust be called as functions (i.e. with nothisvalue). [3.2]thenmay be called multiple times on the same promise.- If/when 
promiseis fulfilled, all respectiveonFulfilledcallbacks must execute in the order of their originating calls tothen. - If/when 
promiseis rejected, all respectiveonRejectedcallbacks must execute in the order of their originating calls tothen. 
- If/when 
 thenmust return a promise [3.3].promise2 = promise1.then(onFulfilled, onRejected);- If either 
onFulfilledoronRejectedreturns a valuex, run the Promise Resolution Procedure[[Resolve]](promise2, x). - If either 
onFulfilledoronRejectedthrows an exceptione,promise2must be rejected witheas the reason. - If 
onFulfilledis not a function andpromise1is fulfilled,promise2must be fulfilled with the same value aspromise1. - If 
onRejectedis not a function andpromise1is rejected,promise2must be rejected with the same reason aspromise1. 
- If either 
 
规范提到,
then方法的两个参数是可选的,但必须是函数,不是函数的话将被忽略。onFulfilled将在promise状态为fulfilled之后调用,promise的value作为它的参数,在promise转为fulfilled之前不允许调用,且只能被调用一次。onRejected类似。两个函数都只能在执行上下文调用栈仅包含
platform code时调用,需要确保两个函数都是异步调用的。两个函数都必须以函数形式调用,不允许包含
this。同一个
promise可以多次调用then方法(此处不是指链式调用),需要确保多次调用是按照顺序执行的。then方法的返回值也是一个promise。onFulfilled或是onRejected的返回值需要执行Promise Resolution Procedure。两个函数若是抛出了错误,
promise2需要以该错误为参数被拒绝。若没有传入两个函数,
promise值需要同样往后续传递。then(onFulfilled, onRejected) { if (this.status === FULFILLED) { onFulfilled(this.value); } if (this.status === REJECTED) { onRejected(this.reason); } }首先简单实现同步回调,判断当前的
status来执行相应的回调。接下来考虑异步执行,若是
executor内部是一个异步执行,异步执行中才会调用resolve或是reject,那么我们在调用then时,status还没有得到转换,此时无法正确执行,且需要在异步地更改status后再执行回调。修改代码:
// ... class MyPromise { constructor(executor) { // ... this.onFulfilledCallList = []; this.onRejectedCallList = []; const resolve = value => { // ... this.onFulfilledCallList.forEach(fn => fn()); }; const reject = reason => { // ... this.onRejectedCallList.forEach(fn => fn()); }; // ... } then(onFulfilled, onRejected) { // ... if (this.status === PENDING) { this.onFulfilledCallList.push(() => { onFulfilled(this.value); }); this.onRejectedCallList.push(() => { onRejected(this.reason); }); } } }此时的异步
resolve已经可以正确执行。再来考虑错误捕获的问题,我们的
executor中可能会抛出错误,此时需要直接执行reject来拒绝。修改代码:
// ... class MyPromise { constructor(executor) { // ... try { executor(resolve, reject); } catch (e) { reject(e); } } // ... }在执行
executor时就尝试捕获错误,若是捕获到了就直接reject,此时需要定义then的第二个参数来处理。此时若是对
promise实例多次调用then方法也可以正确且按序执行。接下来处理两个参数为空的情况。
// ... class MyPromise { // ... then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value; onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; }; // ... } }接下来处理
then的返回值问题,即实现链式调用。参考原生
promise的表现形式:let promise1 = new Promise((resolve, reject) => { setTimeout(() => { resolve(1); }, 1000); }); promise1 .then( res => { console.log('10', res); // 10 1 return '11'; } ) .then( res => { console.log('16', res); // 16 11 return new Promise((resolve, reject) => { resolve('18'); }); } ) .then( res => { console.log('24', res); // 24 18 } );可以看出,每个
then方法的参数都是前一次then的返回值,返回值若是promise,则是resolve的值。于是我们需要处理两种状况,
return了原始值或是promise。于是我们对
onFulfilled和onRejected返回值进行一个处理。在那之前,我们先注意若是
onFulfilled和onRejected函数内部执行时抛出了错误,我们需要进行reject。所以代码修改如下:// ... function promiseResolve(promise2, x, resolve, reject) { console.log(promise2, x, resolve, reject); } class MyPromise { // ... then(onFulfilled, onRejected) { // ... let promise2 = new MyPromise((resolve, reject) => { if (this.status === FULFILLED) { setTimeout(() => { try { let x = onFulfilled(this.value); promiseResolve(promise2, x, resolve, reject); } catch (e) { reject(e); } }, 0); } if (this.status === REJECTED) { setTimeout(() => { try { let x = onRejected(this.reason); promiseResolve(promise2, x, resolve, reject); } catch (e) { reject(e); } }, 0); } if (this.status === PENDING) { this.onFulfilledCallList.push(() => { try { let x = onFulfilled(this.value); promiseResolve(promise2, x, resolve, reject); } catch (e) { reject(e); } }); this.onRejectedCallList.push(() => { try { let x = onRejected(this.reason); promiseResolve(promise2, x, resolve, reject); } catch (e) { reject(e); } }); } }); return promise2; } }此处做了较多修改,在
then方法中定义了一个新的promise实例,然后进行状态判定,若是已完成,则进行相应操作。若是未完成,则把回调进行订阅,在完成时进行发布。调用回调时需要得到返回值,然后进行返回值的处理。于是定义了一个resolvePromise方法来处理,我们会在这个函数内部进行resolve或是reject,所以需要传入对应函数。同时传入promise本身和x这个返回值来自于规范内的规定。但是,在调用
promiseResolve时,我们的promise2还没有执行完毕,所以我们需要使用异步方法setTimeout来延迟这个函数的执行,确保可以正确得到promise2。同时,我们也要对回调函数的执行进行错误捕获,若是捕获到错误就reject。为什么订阅回调时不需要异步来调用处理函数呢?因为订阅的回调发布时已经是异步状态。
然后开始处理
promiseResolve函数。首先看规范:
The promise resolution procedure is an abstract operation taking as input a promise and a value, which we denote as
[[Resolve]](promise, x). Ifxis a thenable, it attempts to makepromiseadopt the state ofx, under the assumption thatxbehaves at least somewhat like a promise. Otherwise, it fulfillspromisewith the valuex.This treatment of thenables allows promise implementations to interoperate, as long as they expose a Promises/A+-compliant
thenmethod. It also allows Promises/A+ implementations to “assimilate” nonconformant implementations with reasonablethenmethods.To run
[[Resolve]](promise, x), perform the following steps:If
promiseandxrefer to the same object, rejectpromisewith aTypeErroras the reason.If
xis a promise, adopt its state :- If 
xis pending,promisemust remain pending untilxis fulfilled or rejected. - If/when 
xis fulfilled, fulfillpromisewith the same value. - If/when 
xis rejected, rejectpromisewith the same reason. 
- If 
 Otherwise, if
xis an object or function,Let
thenbex.then. [3.5]If retrieving the property
x.thenresults in a thrown exceptione, rejectpromisewitheas the reason.If
thenis a function, call it withxasthis, first argumentresolvePromise, and second argumentrejectPromise, where:If/when
resolvePromiseis called with a valuey, run[[Resolve]](promise, y).If/when
rejectPromiseis called with a reasonr, rejectpromisewithr.If both
resolvePromiseandrejectPromiseare called, or multiple calls to the same argument are made, the first call takes precedence, and any further calls are ignored.If calling
thenthrows an exceptione,If
resolvePromiseorrejectPromisehave been called, ignore it.Otherwise, reject
promisewitheas the reason.
If
thenis not a function, fulfillpromisewithx.
If
xis not an object or function, fulfillpromisewithx.
If a promise is resolved with a thenable that participates in a circular thenable chain, such that the recursive nature of
[[Resolve]](promise, thenable)eventually causes[[Resolve]](promise, thenable)to be called again, following the above algorithm will lead to infinite recursion. Implementations are encouraged, but not required, to detect such recursion and rejectpromisewith an informativeTypeErroras the reason. [3.6]简单翻译一下:
此处使用
promise2指代第一个参数。如果
promise2和x引用的是同一个对象,直接以类型错误reject。如果
x是一个promise:- 如果 
x的状态是pending,那么promise2需要保持直到x转为fulfilled或者是rejected。 - 如果 
x的状态是fulfilled,使用x的value去转换promise2为fulfilled。 reject同上。
- 如果 
 如果
x是一个对象或函数:- 将 
x.then赋值给then。 - 若是 
x.then的结果抛出了错误,使用这个错误来rejectpromise2。 - 如果 
then是一个函数就调用它,第一个参数为resolvePromise,第二个参数为rejectPromise。resolvePromise接收一个参数y,它的类型也可能是promise,所以调用promiseResolve来处理它。rejectPromise接收一个参数r,使用它来rejectpromise2。- 如果两个回调参数都被调用,或是多次使用相同参数重复调用,只有第一次调用生效,其余的会被忽略。
 - 如果调用 
x.then抛出错误:- 如果回调参数已经被调用了,忽略。
 - 其余情况,使用错误来 
rejectpromise2。 
 
 - 如果 
then不是一个函数,使用x来fulfilledpromise2。 
- 将 
 如果
x是其他类型,使用x来fulfilledpromise2。
// ... function promiseResolve(promise2, x, resolve, reject) { if (promise2 === x) { return reject(new TypeError('Chaining cycle detected for promise #<Promise>')); } let called = false; if ((typeof x === 'object' && x !== null) || typeof x === 'function') { try { let then = x.then; if (typeof then === 'function') { then.call( x, y => { if (called) return; called = true; promiseResolve(promise2, y, resolve, reject); }, r => { if (called) return; called = true; reject(r); } ); } else { resolve(x); } } catch (err) { if (called) return; called = true; reject(err); } } else { resolve(x); } } class MyPromise { // ... }此处已经可以正确处理链式调用,返回新的
promise实例的情况。且嵌套返回也没有问题。let promise1 = new MyPromise((resolve, reject) => { resolve(1); }); let promise2 = promise1.then( res => { return res; }, err => { return err; } ); promise2 .then() .then() .then() .then( res => { console.log('19', res); // 19 1 return new MyPromise((resolve, reject) => { resolve( new MyPromise((resolve, reject) => { resolve('23'); }) ); }); }, err => { console.log(err); } ) .then( res => { console.log('34', res); // 34 19 return new MyPromise((resolve, reject) => { resolve('36'); }); }, err => { console.log('40', err); } ) .then( res => { console.log('45', res); // 45 34 }, err => { console.log('48', err); } );接下来实现
catch方法:// ... class MyPromise { // ... catch(onRejected) { return this.then(null, onRejected); } }如代码所示,
catch的逻辑其实与then类似,只是没有传入onFulfilled回调而已。接下来实现
Promise.resolvePromise.reject方法:// ... class MyPromise { // ... static resolve(onResolved) { return new MyPromise((resolve, reject) => { if (onResolved instanceof MyPromise) { onResolved.then(resolve, reject); } else { resolve(onResolved); } }); } static reject(onRejected) { return new MyPromise((resolve, reject) => { reject(onRejected); }); } }两个函数都返回一个
promise即可。resolve内部需要进行判断,如果传入的值为一个promise,那么我们就进行处理。接下来实现
Promise.all方法:// ... class MyPromise { // ... static all(promises) { let len = promises.length; let values = []; let resolvedCount = 0; return new MyPromise((resolve, reject) => { promises.forEach(p => { MyPromise.resolve(p).then( res => { values.push(res); resolvedCount++; if (resolvedCount === len) { resolve(values); } }, err => { reject(err); } ); }); }); } }all方法等待传入的参数全部执行完成,返回所有返回值的数组。我们遍历参数列表,对每一个参数进行
resolve。将结果加入value数组中,并计数。当返回值数量等于参数数量时,可以resolve返回值。最后实现
race方法,这个方法只需要resolve最先得到结果的值即可。// ... class MyPromise { // ... static race(promises) { return new MyPromise((resolve, reject) => { promises.forEach(p => { MyPromise.resolve(p).then( res => { resolve(res); }, err => { reject(err); } ); }); }); } }
总结
写完以后看了一下时间,完成时间是 15:58 。花了两个半小时,中间也有一些地方写乱了,然后又去查资料。感觉对 promise 理解加深了一些,但又好像还是很混乱。
总的来说, promise 改善了回调地狱的问题,我们可以在回调中将结果抛出,然后在后续链式调用获取结果。