Promise 原理解析与源码实现(遵循 Promise/A+ 规范)

--文末附视频教程+源码 1. 构造函数 new Promise 时,需要传递一个 executor 执行器,执行器立刻执行(同步执行),executor 接受两个参数,分别是 resolve(成功) 和 reject(失败)。 promise 有 3 个状态: - pending:初始状态,既不是成功,也不是失败状态。 - fulfilled:成功状态,意味着操作成功完成。 - rejected:失败状态,意味着操作失败。 const PENDING = Symbol('PENDING') const RESOLVED = Symbol('RESOLVED') const REJECTED = Symbol('REJECTED') // Promise 构造函数 function Promise (executor) { // 当前的状态,默认是 pending this.status = PENDING // 保存回调函数,因为 then 可以调用多次,所以以数组保存 this.onResolvedCallbacks = [] this.onRejectedCallbacks = [] // 成功值 this.value = undefined // 拒绝的原因 this.reason = undefined // resolve、reject 是用来改变状态, // 并且根据 then 方法注册回调函数的顺序依次调用回调函数 // resolve 是执行成功后调用的函数 const resolve = (value) => { // 如果状态不是 pending,说明状态已经改变,不能再发生变化 if (this.status === PENDING) { this.value = value this.status = RESOLVED this.onResolvedCallbacks.forEach(fn => fn()) } } // reject 是执行失败后调用的函数 const reject = (reason) => { if (this.status === PENDING) { this.reason = reason this.status = REJECTED this.onRejectedCallbacks.forEach(fn => fn()) } } // 使用 try...catch... 捕捉代码执行过程中可能抛出的异常 try { // 执行器默认会立即执行 executor(resolve, reject) } catch(e) { // 如果执行时发生错误(包括手动抛出的异常),等同于执行失败 reject(e) } } 2. then 方法实现 实现 Promise 的 then 方法,then 方法有两个可选参数,onFulfilled 和 onRejected,并且必须返回一个 promise 对象。 如果 onFulfilled 或 onRejected 返回的是一个 promise,会自动执行这个 promise,并采用它的状态。如果成功则将成功的结果向外层的下一个 then 传递。 Promise.prototype.then = function(onFulfilled, onRejected) { // onFulfilled 和 onRejected 是可选的,这里需要对不传的时候做兼容处理 // onFulfilled 如果不是函数,就构建一个函数,函数直接返回结果。 onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value; // onRejected 如果不是函数,就构建一个函数,函数直接抛出异常。 onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; } let promise2 = new Promise((resolve, reject) => { // 状态为 resolved 或 rejected 时,主要是 new Promise 时执行器里面调用 resolve/reject 是同步的 if (this.status === RESOLVED) { // 使用 setTimeout (宏任务),确保 onFulfilled 和 onRejected 方法异步执行,也确保 promise2 已经定义, // 如果不使用 setTimeout,会导致执行 resolvePromise(promise2, x, resolve, reject) 时 promise2 未定义而报错。 setTimeout(() => { // try...catch... 捕捉代码错误或手动抛出的异常,报错或异常当作执行失败处理。异步代码的报错无法被外层的 try...catch... 捕获 try { const x = onFulfilled(this.value) // x 可能是 promise 也可能是普通值,x 本次 then 调用中 onFulfilled 或 onRejected 回调函数返回的结果,需要传递给下一个 then 的回调函数 // 使用公共方法 resolvePromise 处理不同情况,并实现 x 值的传递。 resolvePromise(promise2, x, resolve, reject) } catch (e) { reject(e) } }, 0) return } if (this.status === REJECTED) { setTimeout(() => { try { const x = onRejected(this.reason) resolvePromise(promise2, x, resolve, reject) } catch (e) { reject(e) } }, 0) } // 状态为 pending 时,主要是 new Promise 时执行器里面调用 resolve/reject 是异步的 if (this.status === PENDING) { // 因为是异步的,不知道何时执行完成,所以这里先存好回调函数的调用(订阅),等状态改变后再执行(发布) this.onResolvedCallbacks.push(() => { setTimeout(() => { try { const x = onFulfilled(this.value) resolvePromise(promise2, x, resolve, reject) } catch (e) { reject(e) } }, 0) }) this.onRejectedCallbacks.push(() => { setTimeout(() => { try { const x = onRejected(this.reason) resolvePromise(promise2, x, resolve, reject) } catch (e) { reject(e) } }) }) } }) return promise2 } 有部分同学可能会认为 then 是在 promise 状态改变后(即有返回值后)才执行,其实 then 是立即执行,是 onFulfilled 和 onRejected 才在状态改变后执行。 3. Promise Resolution Procedure 的实现 Promise 解决程序(promise resolution procedure) 是一个抽象的操作,需要输入一个 promise 和一个值,我们表示为[[Resolve]](promise, x)。 这里我们定义公用方法 resolvePromise 来实现这个过程。 resolvePromise 主要实现的功能是: 判断 promise2 和 x 是否指向同一对象,如果是 promise2 执行失败并且使用 TypeError 作为执行失败的原因。 例如: const p = new Promise((resolve, reject) => resolve(1)) let promise2 = p.then(() => { // x return promise2 }) 判断 x 是不是一个 promise 对象,如果是就通过调用 resolve/reject 获取状态并向下个 then 传递 。 如果 x 是一个普通对象/值,则直接将 x 作为结果值向下个 then 传递。 下面是代码实现: const resolvePromise = (promise2, x, resolve, reject) => { // 如果 promise2 和 x 指向同一对象, promise2 执行失败并且使用 TypeError 作为执行失败的原因 if (promise2 === x) { return reject(new TypeError('Chaining cycle detected for promise #')) } if ((typeof x === 'object' && x !== null) || typeof x === 'function') { // 防止多次调用成功或者失败 let called; try { // 首先存储一个指向 x.then 的引用,然后测试并调用该引用,以避免多次访问 x.then 属性 // 预防取 x.then 的时候错误,例如: .then 是通过 Object.defineProperty 定义的,定义的 get() {}(getter) 可能代码错误或抛出异常 let then = x.then // 没用 x.then 判断因为怕再次取 .then 的时候出错。例如:通过 Object.defineProperty 定义的 then 可能第一次调用不报错,第二次调用报错或多次调用返回的值可能不同 if (typeof then === 'function') { // 如果 then 是一个函数,则认为 x 是一个 promise,以 x 为 它的 this 调用它, then 调用完成就会取到 x 的状态,采用 x 的状态返回 // 并且传递两个回调函数作为参数,第一个参数是 resolvePromise,第二个参数是 rejectPromise then.call(x, y => { if (called) { return } called = true // y 是 x 调用 then 后成功的结果,采用这个结果 // y 可能还是一个 promise,所以进行递归调用,直到结果是一个普通值 resolvePromise(promise2, y, resolve, reject) }, r => { // r 是调用 x.then 后报错或异常,不再判断是否是 promise,直接传递 if (called) { return } called = true reject(r); // 失败结果向下传递 }); } else { // 普通对象,直接传递给下一个 then resolve(x) } } catch (e) { // 发生代码错误或手动抛出异常,则当执行失败处理并以 e 为失败原因 if (called) { return } called = true reject(e) } } else { // 普通值,直接传递给下一个 then resolve(x) } } 4. deferred 的实现 Promise.defer = Promise.deferred = function () { let dfd = {} dfd.promise = new Promise((resolve, reject) => { dfd.resolve = resolve dfd.reject = reject }) return dfd } deferred 的作用: 使用 promise-aplus-test 工具需要用到这个方法 这个方法可以减少代码嵌套 例如: const Promise = require('./pormise') const fs = require('fs') const readfile = url => { return new Promise((resolve, reject) => { // 一层嵌套 fs.readFile(url, 'utf-8', (err, data) => { // 二层嵌套 if(err) reject(err) resolve(data) }) }) } readfile('./package.json').then(data => console.log(data)) 使用 deferred : const readfile = url => { let dfd = Promise.defer() // 减少了一层嵌套 fs.readFile(url, 'utf-8', (err, data) => { if(err) dfd.reject(err) dfd.resolve(data) }) return dfd.promise } 5. 测试 测试使用工具 promises-aplus-test 安装:npm install -g promises-aplus-test 测试:promise-aplus-test promise.js 使用本文提供的 github源码 则可以直接运行以下命令: // 安装依赖工具 npm install // 运行测试指令 npm run test 6. Promise的其他方法 上面已经实现了 Promise 的核心部分代码,但原生的 Promise 还提供一些其他的方法。 Promise.resolve() Promise.reject() Promise.all() Promise.race() Promise.prototype.catch() Promise.prototype.finally() 1)Promise.resolve() 有时需要将现有对象转为 Promise 对象,Promise.resolve()方法就起到这个作用。 Promise.resolve()等价于下面的写法。 Promise.resolve('foo') // 等价于 new Promise(resolve => resolve('foo')) Promise.resolve方法的参数分成四种情况。 参数是一个promise,Promise.resolve 不做任何修改,原封不动返回 参数是一个 thenable 对象,Promise.resolve方法会将这个对象转为 Promise 对象,然后就立即执行thenable对象的then方法。 参数不是具有 then 方法的对象,或根本就不是对象,Promise.resolve方法返回一个新的 Promise 对象,状态为resolved 不带有任何参数,直接返回一个resolved状态的 Promise 对象。 Promise.resolve = function (param) { if (param instanceof Promise) { return param; } return new Promise((resolve, reject) => { if (param && param.then && typeof param.then === 'function') { setTimeout(() => { param.then(resolve, reject); }); } else { resolve(param); } }); } 2)Promise.reject() Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected。 Promise.reject()方法的参数,会原封不动地作为reject的理由,变成后续方法的参数。这一点与Promise.resolve方法不一致。 Promise.reject = function (reason) { return new Promise((resolve, reject) => { reject(reason); }); } 3)Promise.all() Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。 const p = Promise.all([p1, p2, p3]); 上面代码中,Promise.all()方法接受一个数组作为参数,p1、p2、p3都是 Promise 实例,如果不是,就会先调用下面讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。另外,Promise.all()方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。 p的状态由p1、p2、p3决定,分成两种情况。 (1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。 (2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。 Promise.all = function (promises) { return new Promise((resolve, reject) => { // 存放结果,.all 传入的参数是数组,返回结果也是数据 let result = [] // 使用计数器,记录多个异步并发问题 let index = 0 if (promises.length === 0) { resolve(result) } else { // 处理返回值 function processValue(i, data) { result[i] = data // 计数器记录的个数等于传入的数组长度,说明全部认为已完成,可以返回结果 if (++index === promises.length) { resolve(result) } } for (let i = 0; i < promises.length; i++) { let current = promises[i] // 判断当前的处理对象是 promise 还是普通值 if (isPromise(current)) { // 取当前的处理对象的执行结果,如果有一个执行失败,则直接 reject current.then(data => { processValue(i, data) }, reject) } else { processValue(i, current) } } } }) } 4)Promise.race() Promise.race()方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。 const p = Promise.race([p1, p2, p3]); 上面代码中,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。 Promise.race()方法的参数与Promise.all()方法一样,如果不是 Promise 实例,就会先调用下面讲到的Promise.resolve()方法,将参数转为 Promise 实例,再进一步处理。 Promise.race = function (promises) { return new Promise((resolve, reject) => { if (promises.length === 0) { return; } else { for (let i = 0; i < promises.length; i++) { Promise.resolve(promises[i]).then((data) => { resolve(data); return; }, (err) => { reject(err); return; }); } } }); } 5)Promise.prototype.catch() Promise.prototype.catch 方法是 .then(null, rejection) 或 .then(undefined, rejection) 的别名,用于指定发生错误时的回调函数。 Promise.prototype.catch = function (onRejected) { return this.then(null, onRejected); } 6)Promise.prototype.finally() finally方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的。 Promise.prototype.finally = function (callback) { return this.then((value) => { return Promise.resolve(callback()).then(() => { return value; }); }, (err) => { return Promise.resolve(callback()).then(() => { throw err; }); }); } 参考文档: Promises/A+ 规范(译文) Promises/A+ 规范(官方原文英文版) 阮一峰 - Promise 对象 - ECMAScriptS 6入门 Promise - JavaScript | MDN 获取视频教程+源码

本文章由javascript技术分享原创和收集

发表评论 (审核通过后显示评论):