对于promise的理解,我之前仅仅知道如何使用,最近我看了对于promise的详细解读,发现其实我对promise的理解太肤浅了。故写下这篇blog来梳理一下对promise的理解。同时也会结合网上的blog去实现一个稍微复杂点的promise。

promise实现 step by step

框架

  • Promise构造函数接受一个函数参数exector,exector接受resolvereject两个函数并立即执行,通过resolve/reject改变状态
  • 状态改变后,触发原型链上的then、catch方法 ```javascript function Promise(exector){ const resolve = value => {

    }

    const reject = reason => {

    } }

Promise.prototype.then = function(){} Promise.prototype.catch = function(){}

  1. <a name="mPGTd"></a>
  2. ## 一个能work的雏形
  3. - 定义三种状态、初始状态为`PENDING`,resolve和reject分别改变其状态。并将value值改变。
  4. - 将resolve和reject作为exector参数,调用exector并执行。并对其进行try..catch
  5. - then方法接受两个回调函数,作为状态改变后执行的回调。并用setTimeout模拟微任务。
  6. ```javascript
  7. // 定义三种状态
  8. const PENDING = 'PENDING'; // 进行中
  9. const FULFILLED = 'FULFILLED'; // 已成功
  10. const REJECTED = 'REJECTED'; // 已失败
  11. function Promise(exector) {
  12. // 初始化状态
  13. this.status = PENDING;
  14. // 将成功、失败结果放在this上,便于then、catch访问
  15. this.value = undefined;
  16. this.reason = undefined;
  17. const resolve = value => {
  18. // 只有进行中状态才能更改状态
  19. if (this.status === PENDING) {
  20. this.status = FULFILLED;
  21. this.value = value;
  22. }
  23. }
  24. const reject = reason => {
  25. // 只有进行中状态才能更改状态
  26. if (this.status === PENDING) {
  27. this.status = REJECTED;
  28. this.reason = reason;
  29. }
  30. }
  31. try {
  32. // 立即执行executor
  33. // 把内部的resolve和reject传入executor,用户可调用resolve和reject
  34. exector(resolve, reject);
  35. } catch(e) {
  36. // executor执行出错,将错误内容reject抛出去
  37. reject(e);
  38. }
  39. }
  40. Promise.prototype.then = function(onFulfilled, onRejected) {
  41. // then是微任务,这里用setTimeout模拟
  42. setTimeout(() => {
  43. if (this.status === FULFILLED) {
  44. // FULFILLED状态下才执行
  45. onFulfilled(this.value);
  46. } else if (this.status === REJECTED) {
  47. // REJECTED状态下才执行
  48. onRejected(this.reason);
  49. }
  50. })
  51. }

处理异步

想象一个这样的场景:

  1. let promise = new Promise((resolve, reject) => {
  2. setTimeout(() => {
  3. resolve('success');
  4. }, 1000)
  5. })
  6. promise.then(value => {
  7. console.log(value)
  8. })

resolve方法是通过异步来调用。按照上面写的代码,调用then方法的时候,状态还没有改变。等到resolve方法被调用时,没有回调来响应。

如何处理异步呢?我们应该想到既然是resolve或者reject方法来改变状态,而状态改变就应该立即执行then方法,那么then方法的调用权是不是应该交给resolve方法才是?但是api暴露出去的是给promise实例对象调用。

这里,我们借助观察者模式的思想,在执行then时候,如果当前状态是PENNDING把回调函数保存起来, 在resolve方法里面调用。

  1. // 定义三种状态
  2. const PENDING = 'PENDING'; // 进行中
  3. const FULFILLED = 'FULFILLED'; // 已成功
  4. const REJECTED = 'REJECTED'; // 已失败
  5. function Promise(exector) {
  6. // 初始化状态
  7. this.status = PENDING;
  8. // 将成功、失败结果放在this上,便于then、catch访问
  9. this.value = undefined;
  10. this.reason = undefined;
  11. this.onFulfilledCallbacks = undefined;
  12. this.onRejectedCallbacks = undefined;
  13. const resolve = value => {
  14. // 只有进行中状态才能更改状态
  15. if (this.status === PENDING) {
  16. this.status = FULFILLED;
  17. this.value = value;
  18. this.onFulfilledCallbacks(this.value);
  19. }
  20. }
  21. const reject = reason => {
  22. // 只有进行中状态才能更改状态
  23. if (this.status === PENDING) {
  24. this.status = REJECTED;
  25. this.reason = reason;
  26. this.onRejectedCallbacks(this.reason);
  27. }
  28. }
  29. try {
  30. // 立即执行executor
  31. // 把内部的resolve和reject传入executor,用户可调用resolve和reject
  32. exector(resolve, reject);
  33. } catch(e) {
  34. // executor执行出错,将错误内容reject抛出去
  35. reject(e);
  36. }
  37. }
  38. Promise.prototype.then = function(onFulfilled, onRejected) {
  39. // then是微任务,这里用setTimeout模拟
  40. setTimeout(() => {
  41. if (this.status === FULFILLED) {
  42. onFulfilled(this.value);
  43. } else if (this.status === REJECTED) {
  44. onRejected(this.reason);
  45. } else {
  46. // 存储成功/失败回调
  47. this.onFulfilledCallbacks = onFulfilled;
  48. this.onRejectedCallbacks = onRejected;
  49. }
  50. })
  51. }

多次调用

所谓多次调用,就是如下场景:

  1. let promise = new Promise((resolve, reject) => {
  2. setTimeout(() => {
  3. resolve('success');
  4. }, 1000)
  5. })
  6. promise.then(value => {
  7. console.log(value)
  8. })
  9. promise.then(value2 => {
  10. console.log(value2)
  11. })
  12. promise.then(value3 => {
  13. console.log(value3)
  14. })
  15. /** output should be like:
  16. success
  17. success
  18. success
  19. */

但按照上面的代码,只会输出一个success,那是因为只保存了一个then的回调函数,并且靠后的then回调覆盖了前面的then回调。

把接受回调的变量改为数组解决问题:

  1. // 定义三种状态
  2. const PENDING = 'PENDING'; // 进行中
  3. const FULFILLED = 'FULFILLED'; // 已成功
  4. const REJECTED = 'REJECTED'; // 已失败
  5. function Promise(exector) {
  6. // 初始化状态
  7. this.status = PENDING;
  8. // 将成功、失败结果放在this上,便于then、catch访问
  9. this.value = undefined;
  10. this.reason = undefined;
  11. // 成功态回调函数队列
  12. this.onFulfilledCallbacks = [];
  13. // 失败态回调函数队列
  14. this.onRejectedCallbacks = [];
  15. const resolve = value => {
  16. // 只有进行中状态才能更改状态
  17. if (this.status === PENDING) {
  18. this.status = FULFILLED;
  19. this.value = value;
  20. // 成功态函数依次执行
  21. this.onFulfilledCallbacks.forEach(fn => fn(this.value));
  22. }
  23. }
  24. const reject = reason => {
  25. // 只有进行中状态才能更改状态
  26. if (this.status === PENDING) {
  27. this.status = REJECTED;
  28. this.reason = reason;
  29. // 失败态函数依次执行
  30. this.onRejectedCallbacks.forEach(fn => fn(this.reason))
  31. }
  32. }
  33. try {
  34. // 立即执行executor
  35. // 把内部的resolve和reject传入executor,用户可调用resolve和reject
  36. exector(resolve, reject);
  37. } catch(e) {
  38. // executor执行出错,将错误内容reject抛出去
  39. reject(e);
  40. }
  41. }
  42. Promise.prototype.then = function(onFulfilled, onRejected) {
  43. // then是微任务,这里用setTimeout模拟
  44. setTimeout(() => {
  45. if (this.status === FULFILLED) {
  46. onFulfilled(this.value);
  47. } else if (this.status === REJECTED) {
  48. onRejected(this.reason);
  49. } else {
  50. // 存储成功/失败回调
  51. this.onFulfilledCallbacks.push(onFulfilled);
  52. this.onRejectedCallbacks.push(onRejected);
  53. }
  54. })
  55. }

链式调用

下面这种形式就是链式调用,注意区别多次调用哦。

  1. let promise = new Promise((resolve, reject) => {
  2. setTimeout(() => {
  3. resolve('success');
  4. }, 1000)
  5. })
  6. promise.then(value => {
  7. console.log(value)
  8. }).then(value2 => {
  9. console.log(value2)
  10. }).then(value3 => {
  11. console.log(value3)
  12. })

链式调用可以在then后继续接上then,从而无限套娃。这就要求then方法必须要返回一个promise实例对象。因此,我们在then方法中,整体框架如下所示:

  1. Promise.prototype.then = function(onFulfilled, onRejected) {
  2. const self = this;
  3. let p2 = new Promise((resolve, reject) => {
  4. if (self.status === PENDING) {
  5. ...
  6. } else if (self.status === FULFILLED) {
  7. ...
  8. } else {
  9. ...
  10. }
  11. })
  12. return p2;
  13. }

既然是一个promise实例了,肯定就要考虑什么时候调用resolve方法,和reject方法。

如果此时,self.status的状态为PENNDING,我们需要将onFulfilled``onRejected这两个回调push进对应的队列。直接就这样push进去吗?

HELL NO!! 我连这两个回调返回什么都不知道,万一返回的是一个异步操作呢?例如promise实例对象,那么是不是我这个p2的resolve方法调用时机应该在返回的promise实例对象完成后才能resolve?

因此,我们需要对push进队列的方法进行如下改造:

  1. // 模拟微任务
  2. setTimeout(() => {
  3. const result = onFulfilled(self.value);
  4. result instanceof Promise ? result.then(resolve, reject) : resolve(result);
  5. })

首先我们应该先执行onFuilfilled方法拿到返回值检查下是不是promise,不是promise就直接调用resolve方法(这里resolve方法是p2的)
如果是promise,就应该等待他执行完,根据result这个promise的结果来判断调用resolve还是reject,所以这里把p2的resolvereject作为then方法的回调函数。

完整then方法如下:

  1. Promise.prototype.then = function (onFulfilled, onRejected) {
  2. // 保存this,为了获得外层promise对象的成员变量
  3. const self = this;
  4. const promise2 = new Promise((resolve, reject) => {
  5. // 针对promise内异步执行resolve
  6. if (self.status === PENDING) {
  7. self.onFulfilledCallbacks.push(() => {
  8. // try捕获错误
  9. try {
  10. // 模拟微任务
  11. setTimeout(() => {
  12. const result = onFulfilled(self.value);
  13. // 分两种情况:
  14. // 1. 回调函数返回值是Promise,执行then操作
  15. // 2. 如果不是Promise,调用新Promise的resolve函数
  16. // result如果是promise,则需要等待result结果再确定是resolve还是reject,所以把resolve reject作为
  17. // result.then的回调。注意这里的resolve和reject不是result的resolve和reject。是promise2的
  18. result instanceof Promise ? result.then(resolve, reject) : resolve(result);
  19. })
  20. } catch(e) {
  21. reject(e);
  22. }
  23. });
  24. self.onRejectedCallbacks.push(() => {
  25. // 以下同理
  26. try {
  27. setTimeout(() => {
  28. const result = onRejected(self.reason);
  29. // 不同点:此时是reject
  30. result instanceof Promise ? result.then(resolve, reject) : resolve(result);
  31. })
  32. } catch(e) {
  33. reject(e);
  34. }
  35. })
  36. } else if (self.status === FULFILLED) { // 针对promise内立即执行resolve
  37. setTimeout(() => {
  38. try {
  39. const result = onFulfilled(self.value);
  40. result instanceof Promise ? result.then(resolve, reject) : resolve(result);
  41. } catch(e) {
  42. reject(e);
  43. }
  44. });
  45. } else if (self.status === REJECT){ // 针对promise内立即执行resolve
  46. setTimeout(() => {
  47. try {
  48. const result = onRejected(self.error);
  49. result instanceof Promise ? result.then(resolve, reject) : resolve(result);
  50. } catch(e) {
  51. reject(e);
  52. }
  53. })
  54. }
  55. })
  56. return promise2;
  57. }

值穿透

then参数期望是函数,传入非函数则会发生值穿透。值传透可以理解为,当传入then的不是函数的时候,这个then是无效的。具体表现如下:

  1. new Promise((resolve, reject) => {
  2. setTimeout(() => {
  3. resolve('success');
  4. }, 1000)
  5. }).then(2).then(3).then(value3 => {
  6. console.log(value3)
  7. })
  8. // output: success

原理上是当then中传入的不算函数,则这个promise返回上一个promise的值,这就是发生值穿透的原因,所以只需要对then的两个参数进行设置就行了:

  1. Promise.prototype.then = function (onFulfilled, onRejected) {
  2. // 值穿透,then()参数期待为函数,如果不为函数,那将onFulfilled重置为返回resolve(value)中的value
  3. onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
  4. onRejected = typeof onRejected === 'function'? onRejected:
  5. reason => { throw new Error(reason instanceof Error ? reason.message:reason) }
  6. ...
  7. }

promise完整代码

  1. // 定义三种状态
  2. const PENDING = 'PENDING'; // 进行中
  3. const FULFILLED = 'FULFILLED'; // 已成功
  4. const REJECTED = 'REJECTED'; // 已失败
  5. function Promise(exector) {
  6. // 初始化状态
  7. this.status = PENDING;
  8. // 将成功、失败结果放在this上,便于then、catch访问
  9. this.value = undefined;
  10. this.reason = undefined;
  11. // 成功态回调函数队列
  12. this.onFulfilledCallbacks = [];
  13. // 失败态回调函数队列
  14. this.onRejectedCallbacks = [];
  15. const resolve = value => {
  16. // 只有进行中状态才能更改状态
  17. if (this.status === PENDING) {
  18. this.status = FULFILLED;
  19. this.value = value;
  20. // 成功态函数依次执行
  21. this.onFulfilledCallbacks.forEach(fn => fn(this.value));
  22. }
  23. }
  24. const reject = reason => {
  25. // 只有进行中状态才能更改状态
  26. if (this.status === PENDING) {
  27. this.status = REJECTED;
  28. this.reason = reason;
  29. // 失败态函数依次执行
  30. this.onRejectedCallbacks.forEach(fn => fn(this.reason))
  31. }
  32. }
  33. try {
  34. // 立即执行executor
  35. // 把内部的resolve和reject传入executor,用户可调用resolve和reject
  36. exector(resolve, reject);
  37. } catch(e) {
  38. // executor执行出错,将错误内容reject抛出去
  39. reject(e);
  40. }
  41. }
  42. Promise.prototype.then = function (onFulfilled, onRejected) {
  43. // 值穿透,then()参数期待为函数,如果不为函数,那将onFulfilled重置为返回resolve(value)中的value
  44. onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
  45. onRejected = typeof onRejected === 'function'? onRejected:
  46. reason => { throw new Error(reason instanceof Error ? reason.message:reason) }
  47. // 保存this,为了获得外层promise对象的成员变量
  48. const self = this;
  49. const promise2 = new Promise((resolve, reject) => {
  50. // 针对promise内异步执行resolve
  51. if (self.status === PENDING) {
  52. self.onFulfilledCallbacks.push(() => {
  53. // try捕获错误
  54. try {
  55. // 模拟微任务
  56. setTimeout(() => {
  57. const result = onFulfilled(self.value);
  58. // 分两种情况:
  59. // 1. 回调函数返回值是Promise,执行then操作
  60. // 2. 如果不是Promise,调用新Promise的resolve函数
  61. // result如果是promise,则需要等待result结果再确定是resolve还是reject,所以把resolve reject作为
  62. // result.then的回调。注意这里的resolve和reject不是result的resolve和reject。是promise2的
  63. result instanceof Promise ? result.then(resolve, reject) : resolve(result);
  64. })
  65. } catch(e) {
  66. reject(e);
  67. }
  68. });
  69. self.onRejectedCallbacks.push(() => {
  70. // 以下同理
  71. try {
  72. setTimeout(() => {
  73. const result = onRejected(self.reason);
  74. // 不同点:此时是reject
  75. result instanceof Promise ? result.then(resolve, reject) : resolve(result);
  76. })
  77. } catch(e) {
  78. reject(e);
  79. }
  80. })
  81. } else if (self.status === FULFILLED) { // 针对promise内立即执行resolve
  82. setTimeout(() => {
  83. try {
  84. const result = onFulfilled(self.value);
  85. result instanceof Promise ? result.then(resolve, reject) : resolve(result);
  86. } catch(e) {
  87. reject(e);
  88. }
  89. });
  90. } else if (self.status === REJECT){ // 针对promise内立即执行resolve
  91. setTimeout(() => {
  92. try {
  93. const result = onRejected(self.error);
  94. result instanceof Promise ? result.then(resolve, reject) : resolve(result);
  95. } catch(e) {
  96. reject(e);
  97. }
  98. })
  99. }
  100. })
  101. return promise2;
  102. }
  103. Promise.prototype.catch= function (onRejected) {
  104. return this.then(null, onRejected);
  105. }

promise用法

case1 then(callback), callback中return作用

  1. new Promise((resolve, reject) => {
  2. setTimeout(() => {
  3. resolve('success');
  4. }, 1000)
  5. }).then(value => {
  6. console.log(value)
  7. return 'Yes'
  8. }).then(value => {
  9. console.log(value)
  10. })
  11. /**
  12. output:
  13. success
  14. Yes
  15. */

很显然,根据之前链式调用的代码可知,then(onFulfilled, onRejected)方法中回调函数是这样执行的:

  1. const result = onFulfilled(self.value);
  2. result instanceof Promise ? result.then(resolve, reject) : resolve(result);

如果有返回值(非Promise),那么调用resolve方法,传递给下一个then方法,如果没有return的话,那么这里的result就会得到一个undefined,同样的这个值也会传递到下一个then方法中。

case2 catch方法

catch方法在链式调用中写到最末尾,用于兜底错误处理。其原理就是调用then方法。

  1. new Promise((resolve, reject) => {
  2. setTimeout(() => {
  3. resolve('success');
  4. }, 1000)
  5. }).then(value => {
  6. console.log(value)
  7. return new Promise((resolve, reject) => {
  8. reject('fuck');
  9. })
  10. }).then(value => {
  11. console.log(value)
  12. return 'sss'
  13. }).then(value => {
  14. console.log(value)
  15. }).catch(res => {
  16. console.log(res)
  17. })
  18. /** output:
  19. success
  20. fuck
  21. */

case3 then(表达式)

  1. new Promise((resolve, reject) => {
  2. setTimeout(() => {
  3. resolve('success');
  4. }, 1000)
  5. }).then(
  6. console.log('value')
  7. )

会立即输出value,因为then里面是表达式,不是函数,会当作函数的嵌套调用执行。

参考

大怪腿长一米八的promise系列文章
详细的Promise源码实现,再被面试问到轻松解答
Promise的源码实现(完美符合Promise/A+规范)