|
|
51CTO旗下网站
|
|
移动端

这一次,彻底弄懂Promise原理

Promise 源码不过几百行,我们可以从执行结果出发,分析每一步的执行过程,然后思考其作用即可。

作者:佚名来源:前端大全|2019-11-08 16:05

 

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 = 0len = 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 = 0len = 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)  
【责任编辑:庞桂玉 TEL:(010)68476606】

点赞 0
分享:
大家都在看
猜你喜欢

订阅专栏+更多

骨干网与数据中心建设案例

骨干网与数据中心建设案例

高级网工必会
共20章 | 捷哥CCIE

372人订阅学习

中间件安全防护攻略

中间件安全防护攻略

4类安全防护
共4章 | hack_man

135人订阅学习

CentOS 8 全新学习术

CentOS 8 全新学习术

CentOS 8 正式发布
共16章 | UbuntuServer

282人订阅学习

读 书 +更多

网管员必读-网络管理(第2版)

本书是对《网管员必读—网络管理》第1版的全面重写,因为书中所涉及的两个主要网络操作系统都已升级。本书介绍的是Windows Server 2003 R2...

订阅51CTO邮刊

点击这里查看样刊

订阅51CTO邮刊

51CTO服务号

51CTO官微