我的测试环境: chorme 85.0.4183.83

写作动机:

最近在手写Promise时,在实现过程中发现Promise核心思路并不复杂, 只不过细节众多, 很容易深陷逻辑分支漩涡。因此想和大家分享以下我手写Promise的思路。同时希望各位看官老爷帮我测试一下是否和原生Promise表现一致,先给老爷们跪下了~~QAQ

基础版本

根据Promise用法, 先实现一个基础版本的Promise,具有以下功能:
1. Promise实例具有state属性表示自身状态, 实例具有三种状态: pending、fulfilled、rejected, 初始值为pending
2. Promise实例具有result属性表示结果。初始值为undefined。
3. Promise实例的状态(state)和结果(result)值只能通过resolve,reject修改, 无法通过修改实例属性修改其值,其中state从pending变为settleed状态(fulfilled、rejected)之后, 就不可变了
4. 实例化Promise对象时, 构造函数执行”executor”函数参数, 并传给”executor”函数两个函数:resolve和reject,分别能够将Promise实例的状态(state)变为”fulfilled”和”rejected”, 同时将实例的结果(result)值设为resolve或reject函数接受的参数值。
5. 如果执行executor报错将会返回一个状态为rejected的Promise, 结果为报错信息

  1. const STATE_PENDING = "pending";
  2. const STATE_FULFILLED = "fulfilled";
  3. const STATE_REJECTED = "rejected";
  4. function Promise(executor){
  5. if (typeof executor !== "function") {
  6. throw new Error("Promise resolver undefined is not a function");
  7. }
  8. let _self = this
  9. let _state = STATE_PENDING
  10. let _result = undefined
  11. Object.defineProperties(_self, {
  12. state: {
  13. get: () => _state,
  14. set: () => _state,
  15. enumerable: true
  16. },
  17. result: {
  18. get: () => _result,
  19. set: () => _result,
  20. enumerable: true
  21. }
  22. });
  23. function resolve(newResult) {
  24. if(_state !== STATE_PENDING) {
  25. return
  26. }
  27. _self.state = _state = STATE_FULFILLED
  28. _self.result = _result = newResult
  29. }
  30. function reject(newResult) {
  31. if(_state !== STATE_PENDING) {
  32. return
  33. }
  34. _self.state = _state = STATE_REJECTED
  35. _self.result = _result = newResult
  36. }
  37. try {
  38. executor(resolve, reject)
  39. } catch(e) {
  40. reject(e)
  41. }
  42. }

测试结果:

  1. new Promise() // Uncaught Error: Promise resolver undefined is not a function
  2. console.log(JSON.stringify(new Promise(() => {})))
  3. // {"state":"pending"}
  4. console.log(JSON.stringify(new Promise((resolve) => resolve())))
  5. // {"state":"fulfilled"}
  6. console.log(JSON.stringify(new Promise((resolve, reject) => reject())))
  7. // {"state":"rejected"}
  8. console.log(new Promise(() => {
  9. throw new Error("executor error!")
  10. }))
  11. // {
  12. // result: Error: executor error! at <anonymous>:2:8 at new Promise (<anonymous>:44:4) at <anonymous>:1:9
  13. // state: "rejected"
  14. // }
  15. let promise = new Promise(() => {})
  16. console.log(JSON.stringify(promise)) // {"state":"pending"}
  17. promise.state = "rejected"
  18. promise.result = "error!"
  19. console.log(JSON.stringify(promise)) // {"state":"pending"}
  20. let promise = new Promise((resolve, reject) => {
  21. resolve();
  22. reject();
  23. })
  24. console.log(promise) // {"state":"fulfiiled"}

到目前为止, 一个基础版的Promise的构造函数算是完成了。我们已经能实例化一个Promise,它具有state、result属性,其值只能通过resolve,reject更改, 不能被外部修改。

现在我们开始给Promise添加更多功能。

回调函数

Promise实例能够调用then、catch函数、finally。这三个函数将会给实例注册回调函数。同时返回一个新的Promise实例。

当Promise实例的状态发生改变后, 将异步执行回调函数。如果状态变成fulfilled执行onFulfilled函数, rejected执行onRejected函数。执行异步函数时, 会将Promise实例的result作为参数传入异步函数。

其中then能够注册onFulfilled、onRejecte方法,catch注册onRejected方法。finally和then相似(后面会列出差异)

简单理解:then、finally、catch其实就是给实例注册回调函数,而resolve、reject除了改变自身的state和result还需要异步执行回调函数。

现在我们重构Promise,使其能够使用then、finally、catch注册并异步执行回调函数。

  1. // 修改resolve、reject
  2. function resolve(newResult) {
  3. if (_state !== STATE_PENDING) {
  4. return;
  5. }
  6. _self.state = _state = STATE_FULFILLED;
  7. _self.result = _result = newResult;
  8. setTimeout(handleCallback);
  9. }
  10. function reject(newResult) {
  11. if (_state !== STATE_PENDING) {
  12. return;
  13. }
  14. _self.state = _state = STATE_REJECTED;
  15. _self.result = _result = newResult;
  16. setTimeout(handleCallback);
  17. }
  18. function handleCallback() {
  19. let callback;
  20. switch (_state) {
  21. case STATE_FULFILLED:
  22. callback = _self.callback && _self.callback.onFulfilled;
  23. break;
  24. case STATE_REJECTED:
  25. callback = _self.callback && _self.callback.onRejected;
  26. break;
  27. default:
  28. }
  29. callback(_result);
  30. }
  31. // 新增
  32. Promise.prototype.then = function (onFulfilled, onRejected) {
  33. this.callback = {
  34. onFulfilled,
  35. onRejected
  36. }
  37. };
  38. Promise.prototype.catch = function (onRejected) {
  39. return this.then(null, onRejected);
  40. };
  41. Promise.prototype.finally = function (onFinally) {
  42. return this.then(onFinally, onFinally);
  43. };

链式调用

Promise最大的特点是链式回调,通过链式调用能够将依赖性比较强的异步任务按序执行。通过传统方式回调深渊也能够实现, 但是显而易见, 在写法上Promise更加优雅、简洁易懂。

但是Promise链式回调同样是在手写过程中最棘手的部分, 因为其中有许多细节。

接下来我说一下特征和难点
特征:
1. 不使用finally的情况下,Promise回调函数链式执行,如果执行回调函数成功,那么执行下一个Promise的onFulfilled回调函数,传入当前回调函数返回值。如果执行回调函数失败,那么执行下一个Promise的onRejected回调函数,传入当前回调函数返回值。
2. 使用finally的情况下, 回调函数不会接受上一个回调函数的结果,并且执行下一个Promise的哪个回调函数由上一个Promise的状态决定, 传给下一个回调函数的参数也是上一个Promise的result。
3. 如果返回值是Promise, 如果Promise没有异常,无论是否为finally注册的回调函数,则回调函数将会等到返回的Promise状态settled之后触发。如果返回 实例化Promise过程报错, 那么将不会触发下一个Promise的onReject。

总之就是一句话:finally比较特殊, 需要单独做大量的处理。

现在有以下问题:then、catch、finally给当前Promise实例注册回调函数,返回了一个新的Promise,而回调函数会链式执行。那么当前Promise实例在执行完回调函数之后,是如何执行新的Promise回调函数的?

由于Promise的state和result只能通过resolve和reject函数改变实例state和result的值。

然后,在设计代码的时候,我草率地决定采取单链实现Promise的异步回调。通过then、catch、finally注册回调函数的同时,将新的Promise的resolve,reject函数绑定在当前Promise实例上, 这样就能够做到Promise实例在执行回调函数时,通过执行下一个Promise的resolve和reject函数,控制下一个Promise的state和result。

由于resolve和reject函数会异步执行回调函数,这样就可以实现Promise回调函数会一直沿着链执行回调函数

这个时候我对Promise链式执行过程的有了那么一内内的理解:

  1. 实例化了一个Promise, 在某个时刻会触发resolve或者reject函数
  2. then给Promise实例注册了回调函数(onFulfilled、onRejected),同时返回一个实例化的新的Promise。在使用构造函数实例化新的Promise过程中, 将新的Promise的内部方法resolve和reject的引用保存在Promise实例上, 使得Promise实例能够控制所有新产生的Promise的state和result
  3. catch原理和then一样, 只不过只注册onRejected函数
  4. finally原理同then一样, 只不过onFulfilled和onRejected都是同一个回调函数, 同时在Promise实例上标记finally帮助在执行回调函数时做一些处理
  5. 当resolve,reject触发。同步地更改Promise实例state和result,异步执行内部回调方法handleChainWork。
  6. handleChainWork主要做两件事:同步执行回调函数,异步改变下一个Promise的stae和result(通过resolve、reject)。当下一个Promise的resolve和reject触发, 又开始做第5点。

然后我实现了Promise能够链式调用的代码。主要处理逻辑在handleChainWork中,下面是实现Promise链式调用的代码:

  1. const STATE_PENDING = "pending";
  2. const STATE_FULFILLED = "fulfilled";
  3. const STATE_REJECTED = "rejected";
  4. const defaultCallback = (result) => result;
  5. const handleUncaughtRejection = (err) => console.error(err);
  6. const curry = (fn) => {
  7. const args = [];
  8. return function reducer(param) {
  9. args.push(param);
  10. if (args.length < fn.length) {
  11. return reducer;
  12. }
  13. fn(...args);
  14. };
  15. };
  16. const handleChainWork = function (subscriber, promiseCallback = {}) {
  17. const state = this.state;
  18. const originResult = this.result;
  19. const isFinallyCb = this.isFinallyCb;
  20. let callbackResult;
  21. let deliveredResult;
  22. let callback;
  23. let wakeupChild;
  24. let hasCallbakck;
  25. switch (state) {
  26. case STATE_FULFILLED:
  27. hasCallbakck = !!promiseCallback.onFulfilled;
  28. callback = promiseCallback.onFulfilled || defaultCallback;
  29. wakeupChild = subscriber.resolve;
  30. break;
  31. case STATE_REJECTED:
  32. hasCallbakck = !!promiseCallback.onRejected;
  33. callback =
  34. promiseCallback.onRejected ||
  35. (this.subscribers.length ? defaultCallback : handleUncaughtRejection);
  36. wakeupChild =
  37. ((isFinallyCb || !hasCallbakck) && subscriber.reject) ||
  38. subscriber.resolve;
  39. break;
  40. default:
  41. }
  42. // excute callback
  43. try {
  44. if (isFinallyCb) {
  45. callbackResult = callback();
  46. deliveredResult = originResult;
  47. } else {
  48. deliveredResult = callbackResult = callback(originResult);
  49. }
  50. } catch (e) {
  51. if (isFinallyCb) {
  52. setTimeout(() => wakeupChild(deliveredResult));
  53. throw new Error(e);
  54. } else {
  55. subscriber.reject(e);
  56. }
  57. }
  58. // case callback return a Promise
  59. if (callbackResult instanceof Promise || deliveredResult instanceof Promise) {
  60. let promiseCallback = [subscriber.resolve, subscriber.reject];
  61. if (callbackResult instanceof Promise) {
  62. callbackResult.then(...promiseCallback);
  63. } else {
  64. deliveredResult.then(...promiseCallback);
  65. }
  66. return;
  67. }
  68. setTimeout(() => wakeupChild(deliveredResult));
  69. };
  70. function Promise(executor) {
  71. if (typeof executor !== "function") {
  72. throw new Error("Promise resolver undefined is not a function");
  73. }
  74. // in case "this" point to window in setTimeout
  75. const _self = this;
  76. // state & result is immutable becauseof closure
  77. // but they also can be gotten by this.XXX
  78. let _state = STATE_PENDING;
  79. let _result = undefined;
  80. Object.defineProperties(_self, {
  81. state: {
  82. get: () => _state,
  83. set: () => _state,
  84. enumerable: true
  85. },
  86. result: {
  87. get: () => _result,
  88. set: () => _result,
  89. enumerable: true
  90. }
  91. });
  92. _self.subscribers = [];
  93. _self.callbacks = [];
  94. function handleStateChange(newState, result) {
  95. if (_state !== STATE_PENDING) {
  96. return;
  97. }
  98. _self.result = _result = result;
  99. _self.state = _state = newState;
  100. setTimeout(() => {
  101. _self.subscribers.forEach((subscriber, index) => {
  102. handleChainWork.bind(_self)(subscriber, _self.callbacks[index]);
  103. });
  104. });
  105. }
  106. const resolve = curry(handleStateChange)(STATE_FULFILLED);
  107. const reject = curry(handleStateChange)(STATE_REJECTED);
  108. try {
  109. executor(resolve, reject);
  110. } catch (error) {
  111. reject(error);
  112. }
  113. }
  114. Promise.prototype.then = function (onFulfilled, onRejected) {
  115. return new Promise((next_res, next_rej) => {
  116. this.subscribers.push({ resolve: next_res, reject: next_rej });
  117. this.callbacks.push({ onFulfilled, onRejected });
  118. });
  119. };
  120. Promise.prototype.catch = function (onRejected) {
  121. return this.then(null, onRejected);
  122. };
  123. Promise.prototype.finally = function (onFinally) {
  124. this.isFinallyCb = true;
  125. return this.then(onFinally, onFinally);
  126. };
  127. export default Promise;

这个时候我可牛逼坏了,Promise我也能手写出来了, 可以发个博客装逼了。
但是我却发现大佬们写的Promise全是清一色的队列,我还隐隐约约看到了秘籍(可能是忍者秘籍上的知识点吧),既然是队列, 该不会Promise还可以支持多个回调函数吧?
我这么一试,还真是:

  1. let promise_1 = new Promise((res, rej) =>
  2. Math.random() * 2 > 1 ? res("行动开始") : rej("有内鬼, 交易终止")
  3. );
  4. promise_1.then(() => console.log("代少知道了"));
  5. promise_1.then(() => console.log("小王知道了"));
  6. promise_1.catch((error) => console.log(error));
  7. // 代少知道了
  8. // 小王知道了

Promise - 图1

所以我们需要重新调整思路, 不过大致的思路还是一样的。只不过从单链结构转换成了树状结构。也就是说:

  • 当使用then、catch、finally创建了一个新的Promise:pChild时,需要将注册的回调函数放到当前Promise实例的callbackArray中去, pChild的resolve和reject同样放到当前Promise实例的childStateControllerArray中去。
  • 当Promise状态改变时,异步执行函数,该函数遍历callbackArray数组, 执行handleChainWork(核心工作基本没有发生太多的变化)。

这是实现了Promise支持多个回调函数后的代码:

  1. const STATE_PENDING = "pending";
  2. const STATE_FULFILLED = "fulfilled";
  3. const STATE_REJECTED = "rejected";
  4. const defaultCallback = (result) => result;
  5. const handleUncaughtRejection = (err) => console.error(err);
  6. const curry = (fn) => {
  7. const args = [];
  8. return function reducer(param) {
  9. args.push(param);
  10. if (args.length < fn.length) {
  11. return reducer;
  12. }
  13. fn(...args);
  14. };
  15. };
  16. const handleChildWork = function (child, promiseCallback = {}) {
  17. const state = this.state;
  18. const originResult = this.result;
  19. const isFinallyCb = this.isFinallyCb;
  20. let callbackResult;
  21. let deliveredResult;
  22. let readyCallback;
  23. let wakeupChild;
  24. let hasCallbakck;
  25. switch (state) {
  26. case STATE_FULFILLED:
  27. hasCallbakck = !!promiseCallback.onFulfilled;
  28. readyCallback = promiseCallback.onFulfilled || defaultCallback;
  29. wakeupChild = child.resolve;
  30. break;
  31. case STATE_REJECTED:
  32. hasCallbakck = !!promiseCallback.onRejected;
  33. readyCallback = promiseCallback.onRejected || defaultCallback;
  34. wakeupChild =
  35. ((isFinallyCb || !hasCallbakck) && child.reject) || child.resolve;
  36. break;
  37. default:
  38. }
  39. // excute callback
  40. try {
  41. if (isFinallyCb) {
  42. callbackResult = readyCallback();
  43. deliveredResult = originResult;
  44. } else {
  45. deliveredResult = callbackResult = readyCallback(originResult);
  46. }
  47. } catch (e) {
  48. if (isFinallyCb) {
  49. setTimeout(wakeupChild, 0, deliveredResult);
  50. throw new Error(e);
  51. } else {
  52. child.reject(e);
  53. }
  54. }
  55. // case callback return a Promise
  56. if (callbackResult instanceof Promise || deliveredResult instanceof Promise) {
  57. let promiseCallback = [child.resolve, child.reject];
  58. if (callbackResult instanceof Promise) {
  59. callbackResult.then(...promiseCallback);
  60. } else {
  61. deliveredResult.then(...promiseCallback);
  62. }
  63. return;
  64. }
  65. setTimeout(wakeupChild, 0, deliveredResult);
  66. };
  67. function Promise(executor) {
  68. if (typeof executor !== "function") {
  69. throw new Error("Promise resolver undefined is not a function");
  70. }
  71. // in case "this" point to window in setTimeout
  72. const _self = this;
  73. // state & result is immutable becauseof closure
  74. // but they also can be gotten by this.XXX
  75. let _state = STATE_PENDING;
  76. let _result = undefined;
  77. Object.defineProperties(_self, {
  78. state: {
  79. get: () => _state,
  80. set: () => _state,
  81. enumerable: true
  82. },
  83. result: {
  84. get: () => _result,
  85. set: () => _result,
  86. enumerable: true
  87. }
  88. });
  89. _self.childStateControllerArray = [];
  90. _self.callbackArray = [];
  91. function handleStateChange(newState, result) {
  92. if (_state !== STATE_PENDING) {
  93. return;
  94. }
  95. _self.result = _result = result;
  96. _self.state = _state = newState;
  97. setTimeout(() => {
  98. if (!_self.callbackArray.length && newState === STATE_REJECTED) {
  99. handleUncaughtRejection(result);
  100. return;
  101. }
  102. _self.childStateControllerArray.forEach((child, index) => {
  103. handleChildWork.bind(_self)(child, _self.callbackArray[index]);
  104. });
  105. });
  106. }
  107. const resolve = curry(handleStateChange)(STATE_FULFILLED);
  108. const reject = curry(handleStateChange)(STATE_REJECTED);
  109. try {
  110. executor(resolve, reject);
  111. } catch (error) {
  112. reject(error);
  113. }
  114. }
  115. Promise.prototype.then = function (onFulfilled, onRejected) {
  116. return new Promise((next_res, next_rej) => {
  117. this.childStateControllerArray.push({
  118. resolve: next_res,
  119. reject: next_rej
  120. });
  121. this.callbackArray.push({ onFulfilled, onRejected });
  122. });
  123. };
  124. Promise.prototype.catch = function (onRejected) {
  125. return this.then(null, onRejected);
  126. };
  127. Promise.prototype.finally = function (onFinally) {
  128. this.isFinallyCb = true;
  129. return this.then(onFinally, onFinally);
  130. };
  131. export default Promise;

下面是的代码是reject、resovle、allSettled、all、race静态方法的实现。

  1. const STATE_PENDING = "pending";
  2. const STATE_FULFILLED = "fulfilled";
  3. const STATE_REJECTED = "rejected";
  4. const checkIteratable = (data) => {
  5. if (typeof data[Symbol.iterator] !== "function") {
  6. throw new TypeError(`${typeof data} is not iterable`);
  7. }
  8. return true;
  9. };
  10. // this function will effect the source array
  11. const waitAllPromisesSetteled = (promiseArray, callback) => {
  12. const settledArray = [];
  13. return new Promise((resolve, reject) => {
  14. function handlePromiseItemSettled() {
  15. settledArray.push(this);
  16. if (settledArray.length < promiseArray.length) {
  17. return;
  18. }
  19. callback({ settledArray, resolve, reject });
  20. }
  21. promiseArray.forEach((item, index, arr) => {
  22. // effect the source array
  23. if (!item instanceof Promise) {
  24. item = arr[index] = Promise.resolve(item);
  25. }
  26. if (item.state === STATE_PENDING) {
  27. const promiseCallback = handlePromiseItemSettled.bind(item);
  28. item.then(promiseCallback, promiseCallback);
  29. } else {
  30. settledArray.push(item);
  31. }
  32. if (settledArray.length === promiseArray.length) {
  33. callback({ settledArray, resolve, reject });
  34. }
  35. });
  36. });
  37. };
  38. Promise.resolve = function (data) {
  39. if (data instanceof Object && typeof data.then === "function") {
  40. data = new Promise(data.then);
  41. }
  42. if (data instanceof Promise) {
  43. return data;
  44. }
  45. return new Promise((res) => res(data));
  46. };
  47. Promise.reject = function (result) {
  48. return new Promise((res, rej) => rej(result));
  49. };
  50. Promise.race = function (promiseArray) {
  51. checkIteratable(promiseArray);
  52. return waitAllPromisesSetteled(
  53. promiseArray,
  54. ({ settledArray, resolve, reject }) => {
  55. let fastestItem = settledArray[0];
  56. switch (fastestItem.state) {
  57. case STATE_REJECTED:
  58. reject(fastestItem.result);
  59. break;
  60. case STATE_FULFILLED:
  61. resolve(fastestItem.result);
  62. break;
  63. default:
  64. }
  65. }
  66. );
  67. };
  68. Promise.all = function (promiseArray) {
  69. checkIteratable(promiseArray);
  70. if (!promiseArray.length) {
  71. return new Promise((res) => res([]));
  72. }
  73. const hasPromiseitem = promiseArray.every((item) => item instanceof Promise);
  74. return waitAllPromisesSetteled(
  75. promiseArray,
  76. ({ settledArray, resolve, reject }) => {
  77. if (!hasPromiseitem) {
  78. setTimeout(resolve, 0, promiseArray);
  79. return;
  80. }
  81. const rejectedPromise = settledArray.find(
  82. (item) => item.state === STATE_REJECTED
  83. );
  84. if (rejectedPromise) {
  85. reject(rejectedPromise.result);
  86. return;
  87. }
  88. const results = promiseArray.map((item) => item.result);
  89. resolve(results);
  90. }
  91. );
  92. };
  93. Promise.allSettled = function (promiseArray) {
  94. checkIteratable(promiseArray);
  95. return waitAllPromisesSetteled(promiseArray, ({ resolve }) => {
  96. resolve(promiseArray);
  97. });
  98. };

完整代码在:
https://codesandbox.io/s/promise-3n8yf?file=/src/Promise.js

最后

感谢各位看官老爷在百忙之中抽出时间看我的文章
如果文中代码有和原生Promise表现不一致的地方欢迎大家评论,探讨

最后我再不要脸的要个赞吧
Promise - 图2