这一次,彻底弄懂 Promise 原理

绝地灬酷狼 2023-08-17 15:21 259阅读 0赞

作者声明

本人将迁移至个人公众号「前端Q」及「掘金」平台写文章。博客园的文章将不再及时更新发布。欢迎大家关注公众号「前端Q」及我的掘金主页:https://juejin.im/user/5874526761ff4b006d4fd9a4/posts

Promise 必须为以下三种状态之一:等待态(Pending)、执行态(Fulfilled)和拒绝态(Rejected)。一旦Promise 被 resolve 或 reject,不能再迁移至其他任何状态(即状态 immutable)。

基本过程:

  1. 初始化 Promise 状态(pending)
  2. 执行 then(..) 注册回调处理数组(then 方法可被同一个 promise 调用多次)
  3. 立即执行 Promise 中传入的 fn 函数,将Promise 内部 resolve、reject 函数作为参数传递给 fn ,按事件机制时机处理
  4. Promise里的关键是要保证,then方法传入的参数 onFulfilled 和 onRejected,必须在then方法被调用的那一轮事件循环之后的新执行栈中执行。

真正的链式Promise是指在当前promise达到fulfilled状态后,即开始进行下一个promise.

链式调用

先从 Promise 执行结果看一下,有如下一段代码:

  1. new Promise((resolve, reject) => {
  2. setTimeout(() => {
  3. resolve({ test: 1 })
  4. resolve({ test: 2 })
  5. reject({ test: 2 })
  6. }, 1000)
  7. }).then((data) => {
  8. console.log('result1', data)
  9. },(data1)=>{
  10. console.log('result2',data1)
  11. }).then((data) => {
  12. console.log('result3', data)
  13. })
  14. //result1 { test: 1 }
  15. //result3 undefined

显然这里输出了不同的 data。由此可以看出几点:

  1. 可进行链式调用,且每次 then 返回了新的 Promise(2次打印结果不一致,如果是同一个实例,打印结果应该一致。
  2. 只输出第一次 resolve 的内容,reject 的内容没有输出,即 Promise 是有状态且状态只可以由pending -> fulfilled或 pending-> rejected,是不可逆的。
  3. then 中返回了新的 Promise,但是then中注册的回调仍然是属于上一个 Promise 的。

基于以上几点,我们先写个基于 PromiseA+ 规范的只含 resolve 方法的 Promise 模型:

  1. function Promise(fn){
  2. let state = 'pending';
  3. let value = null;
  4. const callbacks = [];
  5. this.then = function (onFulfilled){
  6. return new Promise((resolve, reject)=>{
  7. handle({ //桥梁,将新 Promise 的 resolve 方法,放到前一个 promise 的回调对象中
  8. onFulfilled,
  9. resolve
  10. })
  11. })
  12. }
  13. function handle(callback){
  14. if(state === 'pending'){
  15. callbacks.push(callback)
  16. return;
  17. }
  18. if(state === 'fulfilled'){
  19. if(!callback.onFulfilled){
  20. callback.resolve(value)
  21. return;
  22. }
  23. const ret = callback.onFulfilled(value) //处理回调
  24. callback.resolve(ret) //处理下一个 promise 的resolve
  25. }
  26. }
  27. function resolve(newValue){
  28. const fn = ()=>{
  29. if(state !== 'pending')return
  30. state = 'fulfilled';
  31. value = newValue
  32. handelCb()
  33. }
  34. setTimeout(fn,0) //基于 PromiseA+ 规范
  35. }
  36. function handelCb(){
  37. while(callbacks.length) {
  38. const fulfiledFn = callbacks.shift();
  39. handle(fulfiledFn);
  40. };
  41. }
  42. fn(resolve)
  43. }

这个模型简单易懂,这里最关键的点就是在 then 中新创建的 Promise,它的状态变为 fulfilled 的节点是在上一个 Promise的回调执行完毕的时候。也就是说当一个 Promise 的状态被 fulfilled 之后,会执行其回调函数,而回调函数返回的结果会被当作 value,返回给下一个 Promise(也就是then 中产生的 Promise),同时下一个 Promise的状态也会被改变(执行 resolve 或 reject),然后再去执行其回调,以此类推下去…链式调用的效应就出来了。

但是如果仅仅是例子中的情况,我们可以这样写:

  1. new Promise((resolve, reject) => {
  2. setTimeout(() => {
  3. resolve({ test: 1 })
  4. }, 1000)
  5. }).then((data) => {
  6. console.log('result1', data)
  7. //dosomething
  8. console.log('result3')
  9. })
  10. //result1 { test: 1 }
  11. //result3

实际上,我们常用的链式调用,是用在异步回调中,以解决”回调地狱”的问题。如下例子:

  1. new Promise((resolve, reject) => {
  2. setTimeout(() => {
  3. resolve({ test: 1 })
  4. }, 1000)
  5. }).then((data) => {
  6. console.log('result1', data)
  7. //dosomething
  8. return test()
  9. }).then((data) => {
  10. console.log('result2', data)
  11. })
  12. function test(id) {
  13. return new Promise(((resolve) => {
  14. setTimeout(() => {
  15. resolve({ test: 2 })
  16. }, 5000)
  17. }))
  18. }
  19. //基于第一个 Promise 模型,执行后的输出
  20. //result1 { test: 1 }
  21. //result2 Promise {then: ƒ}

用上面的 Promise 模型,得到的结果显然不是我们想要的。认真看上面的模型,执行 callback.resolve 时,传入的参数是 callback.onFulfilled 执行完成的返回,显然这个测试例子返回的就是一个 Promise,而我们的 Promise 模型中的 resolve 方法并没有特殊处理。那么我们将 resolve 改一下:

  1. function Promise(fn){
  2. ...
  3. function resolve(newValue){
  4. const fn = ()=>{
  5. if(state !== 'pending')return
  6. if(newValue && (typeof newValue === 'object' || typeof newValue === 'function')){
  7. const {then} = newValue
  8. if(typeof then === 'function'){
  9. // newValue 为新产生的 Promise,此时resolve为上个 promise 的resolve
  10. //相当于调用了新产生 Promise 的then方法,注入了上个 promise 的resolve 为其回调
  11. then.call(newValue,resolve)
  12. return
  13. }
  14. }
  15. state = 'fulfilled';
  16. value = newValue
  17. handelCb()
  18. }
  19. setTimeout(fn,0)
  20. }
  21. ...
  22. }

用这个模型,再测试我们的例子,就得到了正确的结果:

  1. new Promise((resolve, reject) => {
  2. setTimeout(() => {
  3. resolve({ test: 1 })
  4. }, 1000)
  5. }).then((data) => {
  6. console.log('result1', data)
  7. //dosomething
  8. return test()
  9. }).then((data) => {
  10. console.log('result2', data)
  11. })
  12. function test(id) {
  13. return new Promise(((resolve, reject) => {
  14. setTimeout(() => {
  15. resolve({ test: 2 })
  16. }, 5000)
  17. }))
  18. }
  19. //result1 { test: 1 }
  20. //result2 { test: 2 }

显然,新增的逻辑就是针对 resolve 入参为 Promise 的时候的处理。我们观察一下 test 里面创建的 Promise,它是没有调用 then方法的。从上面的分析我们已经知道 Promise 的回调函数就是通过调用其 then 方法注册的,因此 test 里面创建的 Promise 其回调函数为空。

显然如果没有回调函数,执行 resolve 的时候,是没办法链式下去的。因此,我们需要主动为其注入回调函数。

我们只要把第一个 then 中产生的 Promise 的 resolve 函数的执行,延迟到 test 里面的 Promise 的状态为 onFulfilled 的时候再执行,那么链式就可以继续了。所以,当 resolve 入参为 Promise 的时候,调用其 then 方法为其注入回调函数,而注入的是前一个 Promise 的 resolve 方法,所以要用 call 来绑定 this 的指向。

基于新的 Promise 模型,上面的执行过程产生的 Promise 实例及其回调函数,可以用看下表:






























Promise callback
P1 [{onFulfilled:c1(第一个then中的fn),resolve:p2resolve}]
P2 (P1 调用 then 时产生) [{onFulfilled:c2(第二个then中的fn),resolve:p3resolve}]
P3 (P2 调用 then 时产生) []
P4 (执行c1中产生[调用 test ]) [{onFulfilled:p2resolve,resolve:p5resolve}]
P5 (调用p2resolve 时,进入 then.call 逻辑中产生) []

有了这个表格,我们就可以清晰知道各个实例中 callback 执行的顺序是:

c1 -> p2resolve -> c2 -> p3resolve -> [] -> p5resolve -> []

以上就是链式调用的原理了。

reject

下面我们再来补全 reject 的逻辑。只需要在注册回调、状态改变时加上 reject 的逻辑即可。

完整代码如下:

  1. function Promise(fn){
  2. let state = 'pending';
  3. let value = null;
  4. const callbacks = [];
  5. this.then = function (onFulfilled,onRejected){
  6. return new Promise((resolve, reject)=>{
  7. handle({
  8. onFulfilled,
  9. onRejected,
  10. resolve,
  11. reject
  12. })
  13. })
  14. }
  15. function handle(callback){
  16. if(state === 'pending'){
  17. callbacks.push(callback)
  18. return;
  19. }
  20. const cb = state === 'fulfilled' ? callback.onFulfilled:callback.onRejected;
  21. const next = state === 'fulfilled'? callback.resolve:callback.reject;
  22. if(!cb){
  23. next(value)
  24. return;
  25. }
  26. const ret = cb(value)
  27. next(ret)
  28. }
  29. function resolve(newValue){
  30. const fn = ()=>{
  31. if(state !== 'pending')return
  32. if(newValue && (typeof newValue === 'object' || typeof newValue === 'function')){
  33. const {then} = newValue
  34. if(typeof then === 'function'){
  35. // newValue 为新产生的 Promise,此时resolve为上个 promise 的resolve
  36. //相当于调用了新产生 Promise 的then方法,注入了上个 promise 的resolve 为其回调
  37. then.call(newValue,resolve, reject)
  38. return
  39. }
  40. }
  41. state = 'fulfilled';
  42. value = newValue
  43. handelCb()
  44. }
  45. setTimeout(fn,0)
  46. }
  47. function reject(error){
  48. const fn = ()=>{
  49. if(state !== 'pending')return
  50. if(error && (typeof error === 'object' || typeof error === 'function')){
  51. const {then} = error
  52. if(typeof then === 'function'){
  53. then.call(error,resolve, reject)
  54. return
  55. }
  56. }
  57. state = 'rejected';
  58. value = error
  59. handelCb()
  60. }
  61. setTimeout(fn,0)
  62. }
  63. function handelCb(){
  64. while(callbacks.length) {
  65. const fn = callbacks.shift();
  66. handle(fn);
  67. };
  68. }
  69. fn(resolve, reject)
  70. }

异常处理

异常通常是指在执行成功/失败回调时代码出错产生的错误,对于这类异常,我们使用 try-catch 来捕获错误,并将 Promise 设为 rejected 状态即可。

handle代码改造如下:

  1. function handle(callback){
  2. if(state === 'pending'){
  3. callbacks.push(callback)
  4. return;
  5. }
  6. const cb = state === 'fulfilled' ? callback.onFulfilled:callback.onRejected;
  7. const next = state === 'fulfilled'? callback.resolve:callback.reject;
  8. if(!cb){
  9. next(value)
  10. return;
  11. }
  12. try {
  13. const ret = cb(value)
  14. next(ret)
  15. } catch (e) {
  16. callback.reject(e);
  17. }
  18. }

我们实际使用时,常习惯注册 catch 方法来处理错误,例:

  1. new Promise((resolve, reject) => {
  2. setTimeout(() => {
  3. resolve({ test: 1 })
  4. }, 1000)
  5. }).then((data) => {
  6. console.log('result1', data)
  7. //dosomething
  8. return test()
  9. }).catch((ex) => {
  10. console.log('error', ex)
  11. })

实际上,错误也好,异常也罢,最终都是通过reject实现的。也就是说可以通过 then 中的错误回调来处理。所以我们可以增加这样的一个 catch 方法:

  1. function Promise(fn){
  2. ...
  3. this.then = function (onFulfilled,onRejected){
  4. return new Promise((resolve, reject)=>{
  5. handle({
  6. onFulfilled,
  7. onRejected,
  8. resolve,
  9. reject
  10. })
  11. })
  12. }
  13. this.catch = function (onError){
  14. this.then(null,onError)
  15. }
  16. ...
  17. }

Finally方法

在实际应用的时候,我们很容易会碰到这样的场景,不管Promise最后的状态如何,都要执行一些最后的操作。我们把这些操作放到 finally 中,也就是说 finally 注册的函数是与 Promise 的状态无关的,不依赖 Promise 的执行结果。所以我们可以这样写 finally 的逻辑:

  1. function Promise(fn){
  2. ...
  3. this.catch = function (onError){
  4. this.then(null,onError)
  5. }
  6. this.finally = function (onDone){
  7. this.then(onDone,onError)
  8. }
  9. ...
  10. }

resolve 方法和 reject 方法

实际应用中,我们可以使用 Promise.resolve 和 Promise.reject 方法,用于将于将非 Promise 实例包装为 Promise 实例。如下例子:

  1. Promise.resolve({name:'winty'})
  2. Promise.reject({name:'winty'})
  3. // 等价于
  4. new Promise(resolve => resolve({name:'winty'}))
  5. new Promise((resolve,reject) => reject({name:'winty'}))

这些情况下,Promise.resolve 的入参可能有以下几种情况:

  • 无参数 [直接返回一个resolved状态的 Promise 对象]
  • 普通数据对象 [直接返回一个resolved状态的 Promise 对象]
  • 一个Promise实例 [直接返回当前实例]
  • 一个thenable对象(thenable对象指的是具有then方法的对象) [转为 Promise 对象,并立即执行thenable对象的then方法。]

基于以上几点,我们可以实现一个 Promise.resolve 方法如下:

  1. function Promise(fn){
  2. ...
  3. this.resolve = function (value){
  4. if (value && value instanceof Promise) {
  5. return value;
  6. } else if (value && typeof value === 'object' && typeof value.then === 'function'){
  7. let then = value.then;
  8. return new Promise(resolve => {
  9. then(resolve);
  10. });
  11. } else if (value) {
  12. return new Promise(resolve => resolve(value));
  13. } else {
  14. return new Promise(resolve => resolve());
  15. }
  16. }
  17. ...
  18. }

Promise.reject与Promise.resolve类似,区别在于Promise.reject始终返回一个状态的rejected的Promise实例,而Promise.resolve的参数如果是一个Promise实例的话,返回的是参数对应的Promise实例,所以状态不一 定。 因此,reject 的实现就简单多了,如下:

  1. function Promise(fn){
  2. ...
  3. this.reject = function (value){
  4. return new Promise(function(resolve, reject) {
  5. reject(value);
  6. });
  7. }
  8. ...
  9. }

Promise.all

入参是一个 Promise 的实例数组,然后注册一个 then 方法,然后是数组中的 Promise 实例的状态都转为 fulfilled 之后则执行 then 方法。这里主要就是一个计数逻辑,每当一个 Promise 的状态变为 fulfilled 之后就保存该实例返回的数据,然后将计数减一,当计数器变为 0 时,代表数组中所有 Promise 实例都执行完毕。

  1. function Promise(fn){
  2. ...
  3. this.all = function (arr){
  4. var args = Array.prototype.slice.call(arr);
  5. return new Promise(function(resolve, reject) {
  6. if(args.length === 0) return resolve([]);
  7. var remaining = args.length;
  8. function res(i, val) {
  9. try {
  10. if(val && (typeof val === 'object' || typeof val === 'function')) {
  11. var then = val.then;
  12. if(typeof then === 'function') {
  13. then.call(val, function(val) {
  14. res(i, val);
  15. }, reject);
  16. return;
  17. }
  18. }
  19. args[i] = val;
  20. if(--remaining === 0) {
  21. resolve(args);
  22. }
  23. } catch(ex) {
  24. reject(ex);
  25. }
  26. }
  27. for(var i = 0; i < args.length; i++) {
  28. res(i, args[i]);
  29. }
  30. });
  31. }
  32. ...
  33. }

Promise.race

有了 Promise.all 的理解,Promise.race 理解起来就更容易了。它的入参也是一个 Promise 实例数组,然后其 then 注册的回调方法是数组中的某一个 Promise 的状态变为 fulfilled 的时候就执行。因为 Promise 的状态只能改变一次,那么我们只需要把 Promise.race 中产生的 Promise 对象的 resolve 方法,注入到数组中的每一个 Promise 实例中的回调函数中即可。

  1. function Promise(fn){
  2. ...
  3. this.race = function(values) {
  4. return new Promise(function(resolve, reject) {
  5. for(var i = 0, len = values.length; i < len; i++) {
  6. values[i].then(resolve, reject);
  7. }
  8. });
  9. }
  10. ...
  11. }

总结

Promise 源码不过几百行,我们可以从执行结果出发,分析每一步的执行过程,然后思考其作用即可。其中最关键的点就是要理解 then 函数是负责注册回调的,真正的执行是在 Promise 的状态被改变之后。而当 resolve 的入参是一个 Promise 时,要想链式调用起来,就必须调用其 then 方法(then.call),将上一个 Promise 的 resolve 方法注入其回调数组中。

参考资料

  • PromiseA+规范
  • Promise 实现原理精解
  • 30分钟,让你彻底明白Promise原理

完整 Promise 模型

  1. function Promise(fn) {
  2. let state = 'pending'
  3. let value = null
  4. const callbacks = []
  5. this.then = function (onFulfilled, onRejected) {
  6. return new Promise((resolve, reject) => {
  7. handle({
  8. onFulfilled,
  9. onRejected,
  10. resolve,
  11. reject,
  12. })
  13. })
  14. }
  15. this.catch = function (onError) {
  16. this.then(null, onError)
  17. }
  18. this.finally = function (onDone) {
  19. this.then(onDone, onError)
  20. }
  21. this.resolve = function (value) {
  22. if (value && value instanceof Promise) {
  23. return value
  24. } if (value && typeof value === 'object' && typeof value.then === 'function') {
  25. const { then } = value
  26. return new Promise((resolve) => {
  27. then(resolve)
  28. })
  29. } if (value) {
  30. return new Promise(resolve => resolve(value))
  31. }
  32. return new Promise(resolve => resolve())
  33. }
  34. this.reject = function (value) {
  35. return new Promise(((resolve, reject) => {
  36. reject(value)
  37. }))
  38. }
  39. this.all = function (arr) {
  40. const args = Array.prototype.slice.call(arr)
  41. return new Promise(((resolve, reject) => {
  42. if (args.length === 0) return resolve([])
  43. let remaining = args.length
  44. function res(i, val) {
  45. try {
  46. if (val && (typeof val === 'object' || typeof val === 'function')) {
  47. const { then } = val
  48. if (typeof then === 'function') {
  49. then.call(val, (val) => {
  50. res(i, val)
  51. }, reject)
  52. return
  53. }
  54. }
  55. args[i] = val
  56. if (--remaining === 0) {
  57. resolve(args)
  58. }
  59. } catch (ex) {
  60. reject(ex)
  61. }
  62. }
  63. for (let i = 0; i < args.length; i++) {
  64. res(i, args[i])
  65. }
  66. }))
  67. }
  68. this.race = function (values) {
  69. return new Promise(((resolve, reject) => {
  70. for (let i = 0, len = values.length; i < len; i++) {
  71. values[i].then(resolve, reject)
  72. }
  73. }))
  74. }
  75. function handle(callback) {
  76. if (state === 'pending') {
  77. callbacks.push(callback)
  78. return
  79. }
  80. const cb = state === 'fulfilled' ? callback.onFulfilled : callback.onRejected
  81. const next = state === 'fulfilled' ? callback.resolve : callback.reject
  82. if (!cb) {
  83. next(value)
  84. return
  85. }
  86. try {
  87. const ret = cb(value)
  88. next(ret)
  89. } catch (e) {
  90. callback.reject(e)
  91. }
  92. }
  93. function resolve(newValue) {
  94. const fn = () => {
  95. if (state !== 'pending') return
  96. if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {
  97. const { then } = newValue
  98. if (typeof then === 'function') {
  99. // newValue 为新产生的 Promise,此时resolve为上个 promise 的resolve
  100. // 相当于调用了新产生 Promise 的then方法,注入了上个 promise 的resolve 为其回调
  101. then.call(newValue, resolve, reject)
  102. return
  103. }
  104. }
  105. state = 'fulfilled'
  106. value = newValue
  107. handelCb()
  108. }
  109. setTimeout(fn, 0)
  110. }
  111. function reject(error) {
  112. const fn = () => {
  113. if (state !== 'pending') return
  114. if (error && (typeof error === 'object' || typeof error === 'function')) {
  115. const { then } = error
  116. if (typeof then === 'function') {
  117. then.call(error, resolve, reject)
  118. return
  119. }
  120. }
  121. state = 'rejected'
  122. value = error
  123. handelCb()
  124. }
  125. setTimeout(fn, 0)
  126. }
  127. function handelCb() {
  128. while (callbacks.length) {
  129. const fn = callbacks.shift()
  130. handle(fn)
  131. }
  132. }
  133. fn(resolve, reject)
  134. }

最后

觉得内容有帮助可以关注下我的掘金主页:https://juejin.im/user/5874526761ff4b006d4fd9a4/posts

觉得内容有帮助可以关注下我的公众号 「前端Q」,一起学习成长~~

911587-20190909113458928-1042806977.png

转载于:https://www.cnblogs.com/LuckyWinty/p/11490662.html

发表评论

表情:
评论列表 (有 0 条评论,259人围观)

还没有评论,来说两句吧...

相关阅读

    相关 彻底 Java 线程池原理

    概述 这篇文章是我在阅读源码时整理的一些笔记,对源码的关键点进行了比较详细的注释,然后加上一些自己对线程池机制的理解。最终目的是要弄清楚下面这些问题: 线程池有

    相关 彻底HTTP缓存机制及原理

    前言 Http 缓存机制作为 web 性能优化的重要手段,对于从事 Web 开发的同学们来说,应该是知识体系库中的一个基础环节,同时对于有志成为前端架构师的同学来说是必备的知