持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第8天,点击查看活动详情
实现一个 promise
核心描述
本模块中的代码为示例代码,如果想要更完善的版本,建议查看拓展中的 promise-polyfill 的代码实现。
-
模拟实现一个 Promise 的核心
- 支持构造函数中可以执行同步方法
- 支持 .then 的链式调用,且支持 .then 中返回新的 Promise 对象,实现不同 then 中可以返回不同的值
- 支持 .then 的链式调用均为异步(并不是说同步不能实现 .then 的异步执行逻辑,只是要保证 Promise A+ 规范)
- callbacks 执行队列(数组)
- 内部触发 callbacks 执行的时机,即 resolve 执行
-
实现一个 Promise 的步骤
- 先保证基础的构造函数中有 resolve 和 callbacks
- 保证有 then 方法
- 保证可以支持同步和异步的函数执行
- 保证 then 中的方法是通过异步执行
-
扩展 Promise 方法
- .resolve 的实现
- .reject 的实现
- .catch 的实现
- .finally 的实现
- .all 的实现
- .allSettled 的实现
- .race 的实现
- .any 的实现
-
实现源码示例
- 代码不保证能通过 Promise A+ 规范的所有 test,仅作学习使用
- 代码中的扩展方法不保证均能正常使用,仅用于提供思路
- 建议按照上述的 Promise 的核心,以及实现 Promise 的步骤去阅读,重点思考实现的核心点,而非细节点
- 代码如下:
// Promise 的3种状态的枚举 const PROMISE_STATE = { PENEING: 'pending', FULFILLED: 'fulfilled', REJECTED: 'rjgected' } // 模拟 Promise 实现,重点关注“核心”部分,“拓展”部分仅供参考 class MyPromise { callbacks = [] // state 有三个状态,pending - 等待,fulfilled - 完成, state = PROMISE_STATE.PENEING value = null // 核心 构造函数 constructor(fn){ // fn 即 new MyPromise((resovle,reject)=>{}) 中的 (resolve,reject) => {} 方法,这个回调函数是同步执行的逻辑 // 其实就是在使用调用 resolve 时,本质是在调用 MyPromise 中的 _resolve 方法 // 在使用 reject 时,本质是在调用 MyPromise 中的 _reject 方法 // 此方法作用是批量执行链式调用的 then 方法,并把调用 resolve 时的结果返回给下一次链式调用的回调 fn(this._resolve.bind(this), this._reject.bind(this)) } // 核心 MyPromise.then 的实现,链式调用 then(onFulfilled,onRejected){ // 此处的 resolve 其实就是 MyPromise 中的 _resolve , // 用于处理当前 promsie 中完成状态的执行函数,本质就是 this._resolve.bind(this) // reject 同理 // 这里有一个关键的理解,就是通过 _handle 的调用,将此处新返回的 new MyPromise() 的对象,与初始时的 new MyPromise() 也就是调用 .then 的那个对象关联起来了 // 当如到了初始的 new MyPromise() 对象的 callbacks 中,这样才能在触发后续的 then 中的逻辑执行 // 之所以要在此处重新返回一个新的 new MyPromise() 对象,是为了让后续 then 中能独立处理其自身的逻辑,否则后续 then 中的数据,都是初始时 new MyPromise 中返回的数据 return new MyPromise((resolve, reject)=>{ this._handle({ onFulfilled: onFulfilled || null, resolve, onRejected: onRejected || null, reject }) }) } // 核心 方法执行 _handle(callback){ // 如果是 pending 状态,表示是异步逻辑,即还未开始执行 resolve \ reject 的方法 // 因为只要在执行 resolve\reject 方法时,才会改变 MyPromise 内部的 state 状态 // pending 状态不会立即执行,会将要执行的方法放入 callbacks 队列中,当调用者开始执行 resolve\reject 时才会一次触发 callbacks 的数据 if (this.state === PROMISE_STATE.PENEING) { this.callbacks.push(callback) return } // 跟进不同状态,选择不同执行的回调 let cb = this.state === PROMISE_STATE.FULFILLED ? callback.onFulfilled : callback.onRejected // 如果不存在入参的方法,则直接执行 resolve或reject,如 .then().then((rst)=>{}),第一个 then() 即表示 cb 为空的状态 if(!cb) { this.state === PROMISE_STATE.FULFILLED ? callback.resolve(this.value) : callback.reject(this.value) return } // onFulfilled 其实是 then 的入参执行函数 // 此处的 try ... catch ,也保证了,MyPromise 如果执行出错,也不会崩溃 let ret ; try { ret = cb(this.value) } catch(err) { ret = err } finally{ this.state === PROMISE_STATE.FULFILLED ? callback.resolve(ret) : callback.reject(ret) } } // 核心,执行构造函数中的 resolve 方法 // 当执行 Promise 中作为入参的 fn 方法时,满足条件执行 resolve 方法时 // 本质是在执行 _resolve ,即 Promse.then 中收集的链式调用批量执行 // 并将每次执行的 value 传递给下一次 then 中要调用的 resolve 入参 _resolve(value){ // 如果返回值也是 Promise 对象 if (value instanceof MyPromise) { // 其实这里不用 call 重新绑定 then 的函数中的 this 指向,也是没有问题的 value.then.call(value, this._resolve.bind(this), this._reject.bind(this)) return } this.state = PROMISE_STATE.FULFILLED this.value = value // 添加一个 setTimeout 的延迟是因为如果 Promise 初始化时,是通过同步执行的方式调用的 resolve 方法 // 则会导致 then 的链式调用还未开始执行,即 callbacks 数组的长度为空 // 在 ES6 的 Promise 中,其实是通过微任务来实现异步执行,而非 setTimeout 的宏任务 setTimeout(()=>{ while(this.callbacks.length) { const callback = this.callbacks.shift() this._handle(callback) } }) } // 核心,执行构造函数中的 reject 方法 // 内部的 reject 处理 _reject(error) { this.state = PROMISE_STATE.REJECTED; this.value = error setTimeout(() => { while(this.callbacks.length) { const callback = this.callbacks.shift() this._handle(callback) } }) } // 拓展 catch catch(onError) { return this.then(null, onError) } // 拓展 finally finally(onDone){ if(typeof onDone !== 'function') return this.then() return this.then( value => MyPromise.resolve(onDone()).then(()=> value), reason => MyPromise.resolve(onDone()).then(()=>{ throw reason }) ) } // 拓展 resolve static resolve(value){ if(value) { // 如果入参是 Promise 对象,则直接返回入参 if(value instanceof MyPromise) { return value } // 如果入参是 thenable 类型的对象,则对其包装一层 Promise else if (typeof value === 'object' && typeof value.then === 'function') { const then = value.then; return new MyPromise(resolve => { then(resolve) }) } // 如果只是一个正常值数据,则直接返回该值 else { return new MyPromise(resolve => resolve(value)) } } // 如果入参没有数据,则通过 else { return new MyPromise(resolve => resolve()) } } // 拓展 reject static reject(value){ if(value && typeof value === 'object' && typeof value.then === 'function'){ const then = value.then return new MyPromise((resolve, reject) => { then(reject) }) } else { return new MyPromise((resolve, reject) => reject(value)) } } // 拓展 race static race(promiseList){ return new MyPromise((resolve, reject)=>{ for(let i=0; i< promiseList.length; i++){ MyPromise.resolve(promiseList[i]).then((value)=>{ return resolve(value) }, (error)=>{ return reject(value) }) } }) } // 拓展 any static any(promiseList){ const rejectedArr = [] let rejectedTimes = 0 return new MyPromise((resolve, reject) => { if(promise === null || promiseList.length === 0) { reject('无效的参数') } for (let i=0; i< promiseList.length; i++){ let p = promiseList[i] if(p instanceof MyPromise){ p.then((data)=>{ resolve(data) // 使用最先成功的结果 },(err)=>{ rejectedArr[i]=err rejectedTimes ++ // 如果失败了,则保存错误信息,当全失败时,any 才失败 if(rejectedTimes === promiseList.length){ reject(rejectedArr) } }) } else { resolve(p) } } }) } // 拓展 all static all(promiseList){ return new MyPromise((resolve, reject) => { let fulfilledCount = 0 const length = promiseList.length const rets = Array.from({length: length}) promiseList.forEach((item ,index) => { MyPromise.resolve(item).then(result => { fulfilledCount ++ rets[index] = result if(fulfilledCount === length) { resolve(rets) } }, reason => reject(reason)) }); }) } // 拓展 allSettled static allSettled(promiseList){ return new MyPromise(resolve => { const data =[], len = promiseList.length let count = len for (let i=0; i< len; i+=1){ const promise = promiseList[i] promise.then(res=>{ data[i] = {status: 'fulfilled', value: res} }, error =>{ data[i] = {status: 'rejected', value: error} }).finally(()=>{ if(!--count) { resolve(data) } }) } }) } } // 调用示例 const p1 =new MyPromise(resolve=>{ console.log('初始化 MyPromise') setTimeout(()=>{ resolve('hello MyPromise!') }) }).then(rst => { console.log('then 1 输出结果:', rst) return new MyPromise(resolve=>{ setTimeout(()=>resolve('hello MyPromise then'),1000) }) }).then(rst=>{ console.log('then 2 输出结果:', rst) }) // 输出示例 // 初始化 MyPromise // then 1 输出结果: hello MyPromise! // then 2 输出结果: hello MyPromise then
知识拓展
- Promise A+ 规范:如果想要深入理解 Promise ,则了解 Promise A+ 规范是最标准的答案,建议阅读参考资料中的英文原文或译文,再反过来去看自己实现的 Promise。
- Promise 的 API,比了解 Promise 的实现更重要的是实际应用中的使用,所以需要更关注下面的 API:
- Promise.all():接受一个 promise 对象的 iterable 类型(Array、Map、Set),返回一个 Promise 对象
- 以输入为 promise 类型的数组为例,如果数组中的 promise 对象都正常返回了 resolve ,则会在所有 promise 都完成时,触发 Promise.all 方法,它会返回输入的所有 promise 的 resolve 回调的结果数组。
- 如果输入的 promise 对象中有一个发生异常,或触发了 reject 回调,则 Promise.all 不会关系是否输入的 promise 对象都已经执行完成,而是直接抛出错误,并且 reject 的是第一个抛出的错误信息
- Promise.allSettled():和 Promise.all 类似,唯一的区别是,此方法会等所有输入的 promise 对象都执行了 resolve 或 reject (或抛出异常),之后按输入顺序返回每个 promise 的结果。
- Promise.any():入参是 promise 对象的 iterable 类型,返回结果是入参的 promise 中的第一个成功的 resolve 对象,这里的“第一个”表示的是第一个成功的 promise 对象,而非顺序上的第一个。同时会忽略其他成功或失败的返回结果。如果传入的 promise 对象都失败,则会返回异步失败和一个 AggregateError 对象,继承自 Error,有一个 errors 属性,属性值是由所有失败值填充的数组。(一个成功就成功,全部失败才失败)
- Promise.prototype.catch():catch 方法可以用于您的 promise 组合中的错误处理。
- Promise.prototype.finally():
- 返回一个 Promise。在 promise 结束时,无论结果是 fulfilled 或者是 rejected,都会执行指定的回调函数。
- 在 finally() 方法后面继续 .then() ,这个 then 依然会执行,只不过无法拿到 finally 中 return 的数据
- Promise.race():方法返回一个 promise,一旦迭代器中的某个 promise 解决或拒绝,返回的 promise 就会解决或拒绝。
- Promise.reject():方法返回一个带有拒绝原因的 Promise 对象。
- Promise.resolve():静态方法 Promise.resolve 返回一个解析过的 Promise 对象。
- Promise.prototype.then():方法返回一个 Promise。它最多需要有两个参数:Promise 的成功和失败情况的回调函数。
- Promise.all():接受一个 promise 对象的 iterable 类型(Array、Map、Set),返回一个 Promise 对象
- promise-polyfill 核心实现:本来想贴代码,后来觉得不如直接去看代码库,所以感兴趣的话可以直接查看源码:github.com/taylorhakes…
- 此库的源码更符合 Promise A+ 规范
- 考虑的方面更全,所以会有很多边界判断
- 可以直接拿来项目中使用,也具有学习价值
- 其他
- Promise 的链式调用过程中,如果某个 then 方法报错了,则会中断后续的 then 方法,直接跳到 catch 的处理方法中,但是 catch 后的 then 方法不受影响
- Promise 对象用于表示一个异步操作的最终完成 (或失败)及其结果值。
- 一个 Promise 必然处于以下几种状态之一:
- 待定(pending): 初始状态,既没有被兑现,也没有被拒绝。
- 已兑现(fulfilled): 意味着操作成功完成。
- 已拒绝(rejected): 意味着操作失败。
- Promise 需要支持 .then 的方法
- 需要确保onFulfilled和onRejected异步地执行,并且应该在then方法被调用的那一轮事件循环之后用新的执行栈执行。这可以用如setTimeout或setImmediate这样的“宏任务”机制实现,或者用如MutationObserver或process.nextTick这样的“微任务”机制实现。
- 每次 .then 之后的对象,是一个新的 Promise,这样才可以把每次 .then 中处理的数据再传递下去,否则传递的都是第一个 Promise 中返回的数据
- 实现执行 Promise 构造函数中的同步方法的逻辑,之所以需要用类似 setTimeout 的异步方法包装,本质上是为了符合 Promise A+ 规范,如果不考虑这个规范对于同步方法异步执行的要求,也可以不用 setTimeout 来包一层,只需要用内部的 state 状态来区分即可
- 杂谈:其实之所以我们花时间去学习 Promise 的实现,我思考下来,无非这几点:
- 面试
- 学习 ES5 到 ES6 新增这个 API 的历史逻辑
- 我认为更重要的是,多花些时间去掌握 Promise 的 API ,以及它的执行时机,也许意义更大一些
参考资料
- promise A+ 规范:promisesaplus.com/
- [译]Promise/A+ 规范:zhuanlan.zhihu.com/p/143204897
- MDN-Promise:developer.mozilla.org/zh-CN/docs/…
- 图解 Promise 实现原理(一)—— 基础实现:zhuanlan.zhihu.com/p/58428287
- Promise-polyfill 源码:github.com/taylorhakes…
- 这一次,彻底弄懂 Promise 原理:juejin.cn/post/684490…
- 实现Promise.allSettled: zhuanlan.zhihu.com/p/281871686
- 【Promise 源码学习】第十三篇 - Promise.allsettled 和 Promise.any 的实现:xie.infoq.cn/article/02a…
浏览知识共享许可协议
本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。