图解 Promise 实现原理(四)—— Promise 静态方法实现
本文首发于 vivo互联网技术 微信公众号
链接: https://mp.weixin.qq.com/s/Lp_5BXdpm7G29Z7zT_S-bQ
作者:Morrain
了用法,原生提供了Promise对象。更多关于 Promise 的介绍请参考阮一峰老师的 ES6入门 之 Promise 对象。
很多同学在学习 Promise 时,知其然却不知其所以然,对其中的用法理解不了。本系列文章由浅入深逐步实现 Promise,并结合流程图、实例以及动画进行演示,达到深刻理解 Promise 用法的目的。
本系列文章有如下几个章节组成:
-
图解 Promise 实现原理(四)—— Promise 静态方法实现
一、前言
上一节中,实现了 Promise 的原型方法。包括增加异常状态,catch以及 finally。截至目前,Promise 的实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | class Promise { callbacks = []; state = 'pending' ; //增加状态 value = null ; //保存结果 constructor(fn) { fn( this ._resolve.bind( this ), this ._reject.bind( this )); } then(onFulfilled, onRejected) { return new Promise((resolve, reject) => { this ._handle({ onFulfilled: onFulfilled || null , onRejected: onRejected || null , resolve: resolve, reject: reject }); }); } catch (onError) { return this .then( null , onError); } finally(onDone) { if ( typeof onDone !== 'function' ) return this .then(); let Promise = this .constructor; return this .then( value => Promise.resolve(onDone()).then(() => value), reason => Promise.resolve(onDone()).then(() => { throw reason }) ); } _handle(callback) { if ( this .state === 'pending' ) { this .callbacks.push(callback); return ; } let cb = this .state === 'fulfilled' ? callback.onFulfilled : callback.onRejected; if (!cb) { //如果then中没有传递任何东西 cb = this .state === 'fulfilled' ? callback.resolve : callback.reject; cb( this .value); return ; } let ret; try { ret = cb( this .value); cb = this .state === 'fulfilled' ? callback.resolve : callback.reject; } catch (error) { ret = error; cb = callback.reject } finally { cb(ret); } } _resolve(value) { if (value && ( typeof value === 'object' || typeof value === 'function' )) { var then = value.then; if ( typeof then === 'function' ) { then.call(value, this ._resolve.bind( this ), this ._reject.bind( this )); return ; } } this .state = 'fulfilled' ; //改变状态 this .value = value; //保存结果 this .callbacks.forEach(callback => this ._handle(callback)); } _reject(error) { this .state = 'rejected' ; this .value = error; this .callbacks.forEach(callback => this ._handle(callback)); } } |
接下来再介绍一下 Promise 中静态方法的实现,譬如 Promise.resolve、Promise.reject、Promise.all 和 Promise.race。其它静态方法的实现也是类似的。
二、静态方法
1、Promise.resolve && Promise.reject
除了前文中提到的 Promise实例的原型方法外,Promise 还提供了 Promise.resolve 和Promise.reject 方法。用于将非 Promise 实例包装为 Promise 实例。例如:
1 2 3 | Promise.resolve( 'foo' ) // 等价于 new Promise(resolve => resolve( 'foo' )) |
其实上面的代码是有问题的,如果命中 Id2NameMap 里的值,getNameById 返回的结果就是 name,而不是 Promise 实例。此时 getNameById(id).then 会报错。在我们不清楚返回的是否是 Promise 实例的情况下,就可以使用 Promise.resolve 进行包装:
1 2 3 | Promise.resolve(getNameById(id)).then(name => { console.log(name); }); |
在实现 Promise.resolve 之前,我们先看下它的参数分为哪些情况:
(1)参数是一个 Promise 实例
如果参数是 Promise 实例,那么 Promise.resolve 将不做任何修改、原封不动地返回这个实例。
(2)参数是一个 thenable 对象
thenable 对象指的是具有 then 方法的对象,比如下面这个对象。
1 2 3 4 5 | let thenable = { then: function (onFulfilled) { onFulfilled(42); } }; |
Promise.resolve 方法会将这个对象转为 Promise 对象,然后就立即执行 thenable 对象的 then方法。
1 2 3 4 5 6 7 8 9 10 | let thenable = { then: function (onFulfilled) { onFulfilled(42); } }; let p1 = Promise.resolve(thenable); p1.then( function (value) { console.log(value); // 42 }); |
上面代码中,thenable对象的then方法执行后,对象p1的状态就变为resolved,从而立即执行最后那个then方法指定的回调函数,输出 42。
(3)参数不是具有 then 方法的对象,或根本就不是对象
如果参数是一个原始值,或者是一个不具有then方法的对象,则 Promise.resolve 方法返回一个新的 Promise 对象,状态为 resolved。
(4)不带任务参数
Promise.resolve 方法允许调用时不带参数,直接返回一个 resolved 状态的 Promise 对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | static resolve(value) { if (value && value instanceof Promise) { return value; } else if (value && typeof value === 'object' && typeof value.then === 'function' ) { let then = value.then; return new Promise(resolve => { then(resolve); }); } else if (value) { return new Promise(resolve => resolve(value)); } else { return new Promise(resolve => resolve()); } } |
Promise.reject 与 Promise.resolve 类似,区别在于 Promise.reject 始终返回一个状态的 rejected 的 Promise 实例,而 Promise.resolve 的参数如果是一个 Promise 实例的话,返回的是参数对应的 Promise 实例,所以状态不一定。
2、Promise.all && Promise.race
Promise.all 接收一个 Promise 实例的数组,在所有这些 Promise 的实例都 fulfilled 后,按照 Promise 实例的顺序返回相应结果的数组。
1 2 3 4 5 6 7 8 9 10 11 12 | const p1 = new Promise((resolve, reject) => { setTimeout(() => resolve( 'p1' ), 1000) }) const p2 = new Promise((resolve, reject) => { setTimeout(() => resolve( 'p2' ), 5000) }) Promise.all([p1, p2]).then(rets => { console.log(rets) // ['p1','p2'] }) |
Promise.all 的实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | static all(promises) { return new Promise((resolve, reject) => { let fulfilledCount = 0 const itemNum = promises.length const rets = Array.from({ length: itemNum }) promises.forEach((promise, index) => { Promise.resolve(promise).then(result => { fulfilledCount++; rets[index] = result; if (fulfilledCount === itemNum) { resolve(rets); } }, reason => reject(reason)); }) }) } |
Promise.race 也接收一个 Promise 实例的数组,与 Promise.all不同的是,所以返回的结果是这些 Promise 实例中最先 fulfilled 的。
1 2 3 4 5 6 7 8 9 10 11 12 | const p1 = new Promise((resolve, reject) => { setTimeout(() => resolve( 'p1' ), 1000) }) const p2 = new Promise((resolve, reject) => { setTimeout(() => resolve( 'p2' ), 5000) }) Promise.race([p1, p2]).then(ret => { console.log(ret) // 'p1' }) |
三、总结
刚开始看 Promise 源码的时候总不能很好的理解 then 和 resolve 函数的运行机理,但是如果你静下心来,反过来根据执行 Promise 时的逻辑来推演,就不难理解了。这里一定要注意的点是:Promise 里面的 then 函数仅仅是注册了后续需要执行的代码,真正的执行是在 resolve 方法里面执行的,理清了这层,再来分析源码会省力的多。
现在回顾下 Promise 的实现过程,其主要使用了设计模式中的观察者模式:
-
通过 Promise.prototype.then 和 Promise.prototype.catch 方法将观察者方法注册到被观察者 Promise 对象中,同时返回一个新的 Promise 对象,以便可以链式调用。
-
被观察者管理内部 pending、fulfilled 和 rejected 的状态转变,同时通过构造函数中传递的 resolve 和 reject 方法以主动触发状态转变和通知观察者。
本系列图文讲解的是 Promise 的思想,实现的内容并不能完全满足 Promise/A+ 规范的所有要求。
四、参考资料
更多内容敬请关注 vivo 互联网技术 微信公众号
注:转载文章请先与微信号:Labs2020 联系。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· PostgreSQL 和 SQL Server 在统计信息维护中的关键差异
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· 上周热点回顾(2.17-2.23)
· 如何使用 Uni-app 实现视频聊天(源码,支持安卓、iOS)
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)