Promise 手写记录

516 阅读12分钟

准备工作

手写代码之前,我们先了解一下 Promise ,知道得越多,写出来的代码越健壮。

Promise A+ 规范

很多人会按照这个规范来写,由于我想复习一下 Promise 的相关知识,所以我不会按照这个规范来写,而是根据我所知的知识来写,这样,如果代码跑起来有不同之处,就可以快速发现,达到复习+学习的目的。

Promise 特性:

  1. then / catch 会返回一个 Promise
  2. Promise 状态确定后,不可再变化。
  3. 如果 Promise / then 传入的参数不是函数,将会透传。
  4. Promise 通过 resolve / reject 来更改状态,then 回调函数通过获得同步返回值来返回状态。
  5. 链式调用时,下一个 then 可获得上一个 then 的返回值。
  6. new Promise(cb) 中的 cb 会立刻执行,但 .then(thenCb) 中的 thenCb 不会马上执行,也不会推到微任务队列中,而是等待 Promise resolve 后,才会推到微任务队列中去。同理,根据特性4可知,下一个 then 的回调被推入微任务队列的时机是上一个 then resolve 后。
  7. 根据特性6可知,then 的作用是注册回调函数 ,在 resolvethen 注册的回调函数将会储存在 Promise 中 , resolve 后将会遍历通过 then 注册回调函数,依次将他们推到微任务队列中去,事件循环将回调函数取出来,执行。

下面解释一下,特性1 , 4,7。

第1点: then / catch 会返回一个 Promise 这个会影响两方面:
  1. 链式调用。返回一个 Promise 对象,才能将下一个 then 挂在上面

  2. 请看以下代码:

    // 代码来自 [Promise 链](https://zh.javascript.info/promise-chaining)
    let promise = new Promise(function(resolve, reject) {
      setTimeout(() => resolve(1), 1000);
    });
    
    promise.then(function(result) {
      alert(result); // 1
      return result * 2;
    });
    
    promise.then(function(result) {
      alert(result); // 1
      return result * 2;
    });
    
    promise.then(function(result) {
      alert(result); // 1
      return result * 2;
    });
    
    // 代码来自 [Promise 链](https://zh.javascript.info/promise-chaining)
    new Promise(function(resolve, reject) {
      setTimeout(() => resolve(1), 1000); 
    })
    .then(function(result) {
      alert(result); 
      return result * 2;
    })
    .then(function(result) { 
      alert(result); 
      return result * 2;
    })
    .then(function(result) {
      alert(result); 
      return result * 2;
    });
    

    运行后,会发现第一个 Promise 的结果是 1,1,1,而第二个 Promise 运行的结果是 1,2,4。

    根据特性5可知,第一个 Promise 看似链式调用却不是,第二个才是链式调用。因为第一个 Promisethen 都是挂在 promise 本身上的,而第二个 Promisethen 是依次挂在前一个 then 返回的 Promise 上的,根据特性5,下一个 then 可以获得上一个 then 的返回值。

第4点: Promise 通过 resolve / reject 来更改状态,then 回调函数通过获得同步返回值来返回状态

什么意思呢?

Promise 通过 resolve / reject 来更改状态,这个容易理解,也很常见。

then 回调函数通过获得同步返回值来返回状态,这个的意思就是,then 回调函数只要完成,这个 then 的状态就等同于 resolve 。如:

new Promise((resolve) => resolve(123)).then(res => console.log(res));

console.log(res) 执行完毕,这个 then 的状态就是完成了。

这里额外提示两个信息,其一:

new Promise((resolve) => resolve(123))
	// then1
  .then(res => {
  	new Promise(resolve => resolve(456)).then(_res => console.log(_res));
  	return res;
	})
	// then2
	.then(res => console.log(res, 'then2'))

那么 then2 什么时候执行呢?

答案是:等待 then1 中的 Promise resolve 后才会执行。因为 Promise resolve 后返回一个 Promise ,此时这个 Promise 就是同步返回值了。

所以同步返回值的意思应该是:

  1. 如果是同步代码,那执行完毕同步代码即可
  2. 如果是 Promise ,那要等待这个 Promise resolve 后即可

第2点中,为什么不说是微任务呢?因为就浏览器端,微任务就 PromiseMutationObserver 两个 ,其中 Promise resolve 会返回一个 Promise ,可视为同步返回值,MutationObserver 这个我暂时不了解,所以这个定义的范围我就缩小点,只将已知情况归纳进去。

其二:

new Promise(resolve => resolve(1))
.then(res => {
  return new Promise(resolve => resolve(2)).then(_res => {
    return _res * 2
  }).then(_res => {
    return _res * 3;
  })
}).then(res => console.log(res));

那么外面的 Promise 的第二个 then 什么时候会执行呢?会打印什么内容呢?

答案是:等待里面的 Promise 完全执行完毕后,才会执行。打印 12。

第二个例子跟第一个例子的区别:就是 Promise 是否作为返回值返回。

那这个区别的实质是什么呢?

我的理解是:

then 注册的回调函数不可以返回一个不确定的值。因为 then 会返回一个 Promise (特性1) ,这个不确定的值返回给这个 Promise 后,会导致 Promise 无法 resolve 进而导致下一个 then 流程无法进行。

所以 then 注册的回调函数中如返回一个 Promise ,那就将它完全执行完毕,得到最终值。然后将这个值返回给 then 创建的 Promise 。当然,完全执行完这个 Promise ,也是遵循着 Promise特性事件循环 的。如:

new Promise(resolve => resolve(1))
.then(res => {
  return new Promise(resolve => resolve(2)).then(_res => {
    console.log('inner then1')
    return _res * 2
  }).then(_res => {
    console.log('inner then2')
    return _res * 3;
  })
}).then(res => console.log(res));

new Promise(resolve => resolve('test')).then(res => {
  console.log(res, 'test then1');
  return res;
}).then(res => {
  console.log(res, 'test then2');
  return res;
})
// test test then1
// inner then1
// test test then2
// inner then2
// 12

所以,对于同步返回值的意思,我们重新归纳一下:

  1. then 返回值一定是一个确定的值
  2. 如果是同步代码,那执行完毕同步代码即可
  3. 如果执行的代码是 Promise (非返回值),那要等待这个 Promise resolve 后(即这个 Promisethen 注册的回调事件执行完后返回一个 promise )即可
  4. 如果then 返回值是 Promise ,那么将会将这个 Promise 完全执行完毕(所有 then 注册的回调函数都执行完毕),得到确定的值,这个值就是这个 then 的同步返回值
第7点:根据特性6可知,then 的作用是注册回调函数 ,在 resolvethen 注册的回调函数将会储存在 Promise 中 , resolve 后将会遍历通过 then 注册回调函数,依次将他们推到微任务队列中去,事件循环将回调函数取出来,执行。

先看一段代码,这段代码在上文也有:

// 代码来自 [Promise 链](https://zh.javascript.info/promise-chaining)
let promise = new Promise(function(resolve, reject) {
  setTimeout(() => resolve(1), 1000);
});

promise.then(function(result) {
  alert(result); // 1
  return result * 2;
});

promise.then(function(result) {
  alert(result); // 1
  return result * 2;
});

promise.then(function(result) {
  alert(result); // 1
  return result * 2;
});

Promise resolve 后,可以看到 then 给变量 Promise 注册了三个回调事件。所以他们会被依次推进微任务队列中,等待事件循环取出来执行。

而下面这段代码就不一样了:

// 代码来自 [Promise 链](https://zh.javascript.info/promise-chaining)
new Promise(function(resolve, reject) {
  setTimeout(() => resolve(1), 1000); 
})
.then(function(result) {
  alert(result); 
  return result * 2;
})
.then(function(result) { 
  alert(result); 
  return result * 2;
})
.then(function(result) {
  alert(result); 
  return result * 2;
});

可以看到当 Promise resolve 后,只有一个 then 是挂在该 promise 上的。

且当第一个 then 完成后,将会返回一个新的 Promise ,所以第二个 then 注册的回调事件,是挂在新的 Promise 上的。

所以这两段代码的 then 执行顺序其实是不一样的:

第一段代码,三个then 是被同时推进微任务队列的(因为同挂在一个Promise 上)

第二段代码可以看到,then 是依次挂在上一个 then 返回的 Promise 上的,所以他们是会被依次推进微任务队列中(也就是说上个回调事件推进微任务中执行完了后,下一个 then 注册的回调事件才被推入微任务队列中,此处忽略其他代码的执行,只关注 Promise 的情况)。

乞丐版的实现

function _Promise(cb) {
  this.status = 'pending';
  this.value = '';
  this.reason = '';
  let self = this;

  this.resolve = function (value) {
    // 特性2 `promise` 状态确定后,不可再变化。
    if (self.status === 'pending') {
      self.value = value;
      self.status = 'fulfilled';
    }
  }
  this.reject = function (reason) {
    // 特性2 `promise` 状态确定后,不可再变化。
    if (self.status === 'pending') {
      self.reason = reason;
      self.status = 'rejected';
    }
  }

  cb(this.resolve, this.reject);
}

_Promise.prototype.then = function (cb) {
  if (typeof cb === 'function') {
    if (this.status === 'fulfilled') {
      cb(this.value);
    }
  }
  return this;
}

new _Promise(resolve => resolve(123)).then(res => console.log(res));// 123

显而易见,这种乞丐版的实现,功能非常有限。主要是返回一个新的 Promise 这个重要特性没体现出来。虽然可以链式调用,但所有的 then 均是挂载在 _Promise 实例上,即以下两段代码的效果是等同:

// 第一段
let p = new _Promise(resolve => resolve(123));
p.then(res => console.log(res, 'p1'));
p.then(res => console.log(res, 'p1'));
// 第二段
new _Promise(resolve => resolve(123)).then(res => console.log(res, 'p2')).then(res => console.log(res, 'p2'));
// _Promise: 123 p1 123 p1 123 p2 123 p2
// Promise:123 p1 123 p1 123 p2 undefined p2

丐中丐的实现

function _Promise(cb) {
  this.status = 'pending';
  this.value = '';
  this.reason = '';
  // 缓存方法
  this.resolveCache = [];
  this.rejectCache = [];

  let self = this;

  this.resolve = function (value) {
    // 特性2 `promise` 状态确定后,不可再变化。
    if (self.status === 'pending') {
      self.value = value;
      self.status = 'fulfilled';
      while (self.resolveCache.length) {
        const fn = self.resolveCache.shift();
        const result = typeof fn === 'function' ? fn(self.value) : fn;
        self.value = result;
      }
    }
  }
  this.reject = function (reason) {
    // 特性2 `promise` 状态确定后,不可再变化。
    if (self.status === 'pending') {
      self.reason = reason;
      self.status = 'rejected';
      while (self.rejectCache.length) {
        const fn = self.rejectCache.shift();
        const result = typeof fn === 'function' ? fn(self.reason) : fn;
        self.reason = result;
      }
    }
  }

  cb(this.resolve, this.reject)
  return this;
}

_Promise.prototype.then = function (cb) {
  if (typeof cb === 'function') {
    if (this.status === 'fulfilled') {
      cb(this.value);
    }
    if (this.status === 'pending') {
      this.resolveCache.push(cb);
      this.rejectCache.push(cb);
    }
  }
  return this;
}

new _Promise(resolve => setTimeout(() => resolve(123), 1000))
.then(res => console.log(res))
.then(res => console.log(res, 'then2'))
//  Promise: delay 1s 123 undefined then2
// _Promise: delay 1s 123 undefined then2

缺点依旧在,只是增加了 then 等待 resolve 执行再执行的功能。依旧是乞丐版。

贫民版

以上两个版本,只是热身,下面才是实现。

function _Promise(cb) {
  this.status = 'pending';
  this.value = '';
  this.reason = '';
  // 缓存方法
  this.resolveCache = [];
  this.rejectCache = [];

  let self = this;

  this.resolve = function (value) {
    function run() {
      // 特性2 `promise` 状态确定后,不可再变化。
      if (self.status === 'pending') {
        // 特性5 链式调用时,下一个 `then` 可获得上一个 `then` 的返回值。
        self.value = value;
        self.status = 'fulfilled';
        while (self.resolveCache.length) {
          const fn = self.resolveCache.shift();
          fn(self.value);
        }
      }
    }
    // 特性6,7的体现,如不是这种方式(像 run()),将会连续执行这个promise的 then 注册的所有回调事件然后再到其他的 promise 事件
    // 应该是微任务实现,这里用 setTimeout 模拟
    setTimeout(run);
  }
  this.reject = function (reason) {
    function run() {
      // 特性2 `promise` 状态确定后,不可再变化。
      if (self.status === 'pending') {
        self.reason = reason;
        self.status = 'rejected';
        while (self.rejectCache.length) {
          const fn = self.rejectCache.shift();
          fn(self.reason)
        }
      }
    }
    setTimeout(run);
  }

  cb(this.resolve, this.reject)
}

_Promise.prototype.then = function (onFulfilled, onRejected) {
  let self = this;
  // 特性3 如果 `promise` / `then` 传入的参数不是函数,将会透传。
  if (typeof onFulfilled !== 'function') {
    onFulfilled = function(value) {
      return value;
    }
  }
  if (typeof onRejected !== 'function') {
    onRejected = function(reason) {
      return reason;
    }
  }
  // 特性1 返回一个 _promise
  return new _Promise(function (resolve, reject) {
    this.resolveFn = function (value) {
      try {
        const x = onFulfilled(value);
        // 如果返回了一个promise,则等待 promise 状态变化,否则直接 resolve
        // 因为 then 返回一个 promise,同步返回值需要一个确定的值,所以返回值不可以是 promise 类型的
        // 特性4 `promise` 通过 `resolve` / `reject` 来更改状态,`then` 回调函数通过获得**同步返回值**来返回状态。
        x instanceof _Promise ? x.then(resolve, reject) : resolve(x);
      } catch (e) {
        reject(e);
      }
    };
    this.rejectFn = function (reason) {
      try {
        const x = onRejected(reason);
        x instanceof _Promise ? x.then(resolve, reject) : resolve(x);
      } catch (e) {
        reject(e);
      }
    };

    switch (self.status) {
      case 'pending':
        self.resolveCache.push(this.resolveFn);
        self.rejectCache.push(this.rejectFn);
        break;
      case 'fulfilled':
        this.resolveFn(self.value);
        break;
      case 'rejected':
        this.rejectFn(self.reason);
        break;
    }
  });
}

Promise 的特性全部有所体现。但距离 Promise 的完整实现还是有所欠缺的。很多方法都没实现,如: catch , finally , Promise.resolve() , Promise.all() 等。

小康版

function _Promise(cb) {
  this.status = 'pending';
  this.value = '';
  this.reason = '';
  // 缓存方法
  this.resolveCache = [];
  this.rejectCache = [];

  let self = this;

  const resolve = function (value) {
    function run() {
      // 特性2 `promise` 状态确定后,不可再变化。
      if (self.status === 'pending') {
        // 特性5 链式调用时,下一个 `then` 可获得上一个 `then` 的返回值。
        self.value = value;
        self.status = 'fulfilled';
        while (self.resolveCache.length) {
          const fn = self.resolveCache.shift();
          fn(self.value);
        }
      }
    }
    // 特性6,7的体现,如不是这种方式(像 run()),将会连续执行这个promise的 then 注册的所有回调事件然后再到其他的 promise 事件
    // 应该是微任务实现,这里用 setTimeout 模拟
    setTimeout(run);
  }
  const reject = function (reason) {
    function run() {
      // 特性2 `promise` 状态确定后,不可再变化。
      if (self.status === 'pending') {
        self.reason = reason;
        self.status = 'rejected';
        while (self.rejectCache.length) {
          const fn = self.rejectCache.shift();
          fn(self.reason)
        }
      }
    }
    setTimeout(run);
  }

  cb(resolve, reject)
}

_Promise.prototype.then = function (onFulfilled, onRejected) {
  let self = this;
  // 特性3 如果 `promise` / `then` 传入的参数不是函数,将会透传。
  if (typeof onFulfilled !== 'function') {
    onFulfilled = function(value) {
      return value;
    }
  }
  if (typeof onRejected !== 'function') {
    onRejected = function(reason) {
      return reason;
    }
  }
  // 特性1 返回一个 _promise
  return new _Promise(function (resolve, reject) {
    resolveFn = function (value) {
      try {
        const x = onFulfilled(value);
        // 如果返回了一个promise,则等待 promise 状态变化,否则直接 resolve
        // 因为 then 返回一个 promise,同步返回值需要一个确定的值,所以返回值不可以是 promise 类型的
        // 特性4 `promise` 通过 `resolve` / `reject` 来更改状态,`then` 回调函数通过获得**同步返回值**来返回状态。
        x instanceof _Promise ? x.then(resolve, reject) : resolve(x);
      } catch (e) {
        reject(e);
      }
    };
    rejectFn = function (reason) {
      try {
        const x = onRejected(reason);
        x instanceof _Promise ? x.then(resolve, reject) : resolve(x);
      } catch (e) {
        reject(e);
      }
    };

    switch (self.status) {
      case 'pending':
        self.resolveCache.push(resolveFn);
        self.rejectCache.push(rejectFn);
        break;
      case 'fulfilled':
        resolveFn(self.value);
        break;
      case 'rejected':
        rejectFn(self.reason);
        break;
    }
  });
}

// 该方法是无论 promise resolve 还是 reject ,都会运行的
// 且不会影响 resolve 或 reject 的值
// 因为不知道 promise 最终状态是什么,所以没有参数
_Promise.prototype.finally = function(cb) {
  try {
    return this.then(
      value => new _Promise(resolve => resolve(cb())).then((res) => {
        if (res instanceof _Promise && res.status === 'rejected') {
          return res;
        }
        return value;
      }), 
      error => new _Promise(resolve => resolve(cb())).then((_err) => {
        return _Promise.reject(_err ? _err : error);
      })
    )
  } catch (e) {
    return new _Promise.reject(e);
  }
}

_Promise.prototype.catch = function (rejectFn) {
  return this.then(undefined, rejectFn);
}

_Promise.resolve = function (value) {
  return value instanceof _Promise ? value : new _Promise(resolve => resolve(value));
}

_Promise.reject = function (e) {
  return e instanceof _Promise ? e : new _Promise((undefined, reject) => reject(e));
}

此处要注意,finally 有以下特性:

Promise.prototype.finally() :

注意:finally回调中 throw(或返回被拒绝的promise)将以 throw() 指定的原因拒绝新的promise.

下面对照着代码说明一下:

_Promise.prototype.finally = function(cb) {
  // 这里用上 try {...} catch 是为了收集 throw error
  try {
    return this.then(
      value => new _Promise(resolve => resolve(cb())).then((res) => {
        // 如果 cb 执行后的返回值是_Promise 的实例,且状态是 rejected 的
        // 那就直接返回该实例
        if (res instanceof _Promise && res.status === 'rejected') {
          return res;
        }
        return value;
      }), 
      error => new _Promise(resolve => resolve(cb())).then((_err) => {
        // 如果 本来就是 reject 的话,返回 reject 结果
        // 如果 cb 执行后,返回新的 rejected 值,那将会取代原先的 rejected 值
        return _Promise.reject(_err ? _err : error);
      })
    )
  } catch (e) {
    // 如 catch 到 error 那就直接 reject
    return new _Promise.reject(e);
  }
}

下面来实战一下:

 var p = _Promise.reject('rejected')
  .finally(() => {
    // 正常
    // console.log('finally');
    // error
    // throw Error('这里只有返回被拒绝的 promise 或者 throw 一个错误,才会影响当前 finally 返回的新 promise 的决议');
    // rejected
    return _Promise.reject('这里只有返回被拒绝的 promise 或者 throw 一个错误,才会影响当前 finally 返回的新 promise 的决议');
  })
  .then(value => {
    console.log('成功', value)
  }, (err) => {
    console.log('失败', err)
  });
// result: 
// 失败 这里只有返回被拒绝的 promise 或者 throw 一个错误,才会影响当前 finally 返回的新 promise 的决议
// 如无 finally ,result 将会是:
// 失败 rejected
// 所以在 finally 里,新的 rejected 返回值取代了原先的 rejected 返回值

再看一段代码:

var p = _Promise.resolve('ok')
  .finally(() => {
    // 正常
    // console.log('finally');
    // error
    // throw Error('这里只有返回被拒绝的 promise 或者 throw 一个错误,才会影响当前 finally 返回的新 promise 的决议');
    // rejected
    return _Promise.reject('这里只有返回被拒绝的 promise 或者 throw 一个错误,才会影响当前 finally 返回的新 promise 的决议');
  })
  .then(value => {
    console.log('成功', value)
  }, (err) => {
    console.log('失败', err)
  });
// result:
// 失败 这里只有返回被拒绝的 promise 或者 throw 一个错误,才会影响当前 finally 返回的新 promise 的决议
// fulfilled 状态被更改为 rejected 了

throw error 也会正常处理(也就是结果跟 Promise 结果一致)。

至此,应无其他问题。

相对贫民版来说,这个版本的 Promise 内部的 resolvereject 等方法不再挂在 this 上了。而且增加了 catch , finally ,静态方法 resolve , reject。实现到这里,应该是可以实践上使用了。有空再补上 all , race 方法。

参考

手写Promise——基于es6的Promise实现

Promise 链式调用顺序引发的思考

Promise.prototype.finally()