深入理解Promise
2017-06-04 22:21
302 查看
本文缘起前段时间一朋友换工作时,笔试题中要求手写一个Promise。在工作中虽已大量使用Promise,其原理却没有深入探究过,换做自己,当场也很难手写一个完善的Promise实现。近期补了一些课,以本文来彻底的理解Promise实现原理。
http://www.jianshu.com/p/f8b052c71550
Promise是抽象异步处理对象以及对其进行各种操作的组件,可以将复杂的异步处理轻松的进行模式化。
使用Promise进行异步处理的一个例子
如对Promise的使用尚不了解,推荐阅读JavaScript Promise Cookbook中文版
在掌握了Promise的使用后,推荐继续阅读Promise规范Promise A+ 规范
理解实现原理过程中,阅读了不少相关文章,其中剖析 Promise 之基础篇和JS
Promise的实现原理这两篇文章,个人认为质量较高。本文对原理的理解,也是基于这两篇文章。
借用MDN的图,看看Promise的状态迁移,每个 Promise 存在三个互斥状态:pending、fulfilled、rejected,它们之间的关系是:
最初我是阅读剖析 Promise 之基础篇来学习的,文中初始实现了一个简易版的Promise
这一版本的实现,还是很好理解的。
Promise初始状态为pending、value为null、延迟队列为空。并且作为函数作用域的变量,不向外暴露
传入Promise的函数fn立即执行,将resolve传入fn,fn执行完成调用resolve
resolve被定义为一个内部函数,使用闭包方式来访问value、state、deferreds。遵循Promise规范,resolve方法中,采用异步方式执行延迟队列的方法
promise对象上添加then方法,当前promise对象状态为pending时,将通过then方法注册的新方法,添加到延迟队列;当前promise对象状态为完成时,执行注册的方法
上述简易版本的实现,相信理解起来无压力。但是在理解剖析 Promise 之基础篇文中串行Promise时,着实费了一番脑筋。
初看上述代码时,跟着原文叙述,没能彻底理解其执行流程。通过不断的断点调试,才最终理解。在这一过程中,明白了下面几点
Promise的执行过程可以分为两个阶段,即初始时注册阶段和完成时的resolve阶段
初始时,通过promise.then注册的方法,保存在promise对象的延迟队列。每次调用then方法,返回一个新promis实例,作为链式调用的桥接,这类promise可以乘坐bridge promise
注册的方法执行完,执行resolve时。从当前promise对象延迟队列取出注册的方法继续执行。当注册的方法生成一个新promise实例时,调用then方法注册到对应的延迟队列中;否则依次resolve链式调用中相应的promise实例
将上述过程表达如下
理解上述执行流程,再为上述实现加上错误执行过程和异常处理
通过阅读剖析 Promise 之基础篇,跟进上述代码执行过程,相信已经可以理解Promise实现原理。
在理解过程中,让我思考,上述执行流程到底是哪里不易理解,有没有更好的实现呢?
在上述的实现中,关键方法resolve被定义在Promise中作为内部函数,通过闭包获取promise对象的变量的引用。再将回调函数和resolve方法注册到延迟队列,通过resolve完成了链式的回调。这一过程隐式调用太多,不好理解。
继续阅读其他文章,认为这篇JS Promise的实现原理中有更好的实现。其完整实现可以查看这里。
构造函数定义如下
在 promise 对象中定义了成功回调和失败回调的两个数组,数组中的每一项是通过内部封装的闭包函数调用的结果,也是一个函数。这个函数可以访问到内部调用闭包时传递的 promise 对象,因此通过这种方式也可以访问到我们需要的下一个 promise 以及其关联的成功、失败回调的引用。在then方法中增加闭包调用以及为前一个 promise 对象保存引用。
then方法中调用的makeCallback即上面说到的闭包函数。调用时会把 promise 对象以及相应的回调传递进去,返回一个新的函数。前一个 promise 对象持有返回函数的引用,这样在调用返回函数时,在函数内部就可以访问到 promise 对象以及回调函数了。
resolve和reject函数的实现相对简单
除上述基本方法外,文中还实现了常用静态方法如Promise.all,Promise.race,完整代码如下
相比,剖析 Promise 之基础篇,JS Promise的实现原理文中代码结构要更加清晰,对闭包的调用更为易读。
像resolve(),reject(),run(),makeCallback()这几个工具方法,没有直接定义到Promise构造函数中,作为内部函数,产生隐式的闭包调用,而是提出来作为公共方法,通过参数传递方式将promise对象传入方法。使得代码可读性好,容易理解。
同时,在学习的过程中,读到这篇奇舞团翻译的Bluebird
是如何做到比原生实现更快的?
文中提到了Bluebird相对于原生Promise的几个优化点
函数中的对象分配最小化
减小对象体积
可选特性懒重写
其第一点就是避免在Promise构造函数中,直接定义其他内部函数。除开前面提到的可读性问题,避免这样做,可以避免每次创建Promise对象时,同时创建全新的内部函数对象。
参考文章
JavaScript Promise Cookbook中文版
Promise A+ 规范
剖析 Promise 之基础篇
JS Promise的实现原理
Bluebird
是如何做到比原生实现更快的?
剖析Promise内部结构,一步一步实现一个完整的、能通过所有Test case的Promise类
细嗅promise
http://www.jianshu.com/p/f8b052c71550
1.Promise是什么
Promise是抽象异步处理对象以及对其进行各种操作的组件,可以将复杂的异步处理轻松的进行模式化。使用Promise进行异步处理的一个例子
function getUserId() { return new Promise(function (resolve, reject) { // 异步请求 Y.io('/userid/1', { on: { success: function (id, res) { var o = JSON.parse(res); if (o.status === 1) { resolve(o.id); } else { // 请求失败,返回错误信息 reject(o.errorMsg); } } } }); }); } getUserId().then(function (id) { // do sth with id }, function (error) { console.log(error); });
如对Promise的使用尚不了解,推荐阅读JavaScript Promise Cookbook中文版
在掌握了Promise的使用后,推荐继续阅读Promise规范Promise A+ 规范
2.Promise实现原理
理解实现原理过程中,阅读了不少相关文章,其中剖析 Promise 之基础篇和JSPromise的实现原理这两篇文章,个人认为质量较高。本文对原理的理解,也是基于这两篇文章。
借用MDN的图,看看Promise的状态迁移,每个 Promise 存在三个互斥状态:pending、fulfilled、rejected,它们之间的关系是:
2.1 Promise简易实现
最初我是阅读剖析 Promise 之基础篇来学习的,文中初始实现了一个简易版的Promisefunction Promise(fn) { var state = 'pending', value = null, deferreds = []; this.then = function (onFulfilled) { if (state === 'pending') { deferreds.push(onFulfilled); return this; } onFulfilled(value); return this; }; function resolve(newValue) { value = newValue; state = 'fulfilled'; setTimeout(function () { deferreds.forEach(function (deferred) { deferred(value); }); }, 0); } fn(resolve); } function getUserId() { return new Promise(function (resolve) { resolve(123); }); } getUserId().then(function (id) { console.log('do sth with', id); });
这一版本的实现,还是很好理解的。
Promise初始状态为pending、value为null、延迟队列为空。并且作为函数作用域的变量,不向外暴露
传入Promise的函数fn立即执行,将resolve传入fn,fn执行完成调用resolve
resolve被定义为一个内部函数,使用闭包方式来访问value、state、deferreds。遵循Promise规范,resolve方法中,采用异步方式执行延迟队列的方法
promise对象上添加then方法,当前promise对象状态为pending时,将通过then方法注册的新方法,添加到延迟队列;当前promise对象状态为完成时,执行注册的方法
2.2 串行Promise
上述简易版本的实现,相信理解起来无压力。但是在理解剖析 Promise 之基础篇文中串行Promise时,着实费了一番脑筋。function getUserId() { return new Promise(function (resolve) { window.setTimeout(function () { resolve(9876); }); }); } function getUserMobileById(id) { return new Promise(function (resolve) { console.log('start to get user mobile by id:', id); window.setTimeout(function () { resolve(13810001000); }); }); } getUserId() .then(getUserMobileById) .then(function (mobile) { console.log('do sth with', mobile); }); function Promise(fn) { var state = 'pending', value = null, deferreds = []; this.then = function (onFulfilled) { return new Promise(function (resolve) { handle({ onFulfilled: onFulfilled || null, resolve: resolve }); }); }; function handle(deferred) { if (state === 'pending') { deferreds.push(deferred); return; } var ret = deferred.onFulfilled(value); 14807 deferred.resolve(ret); } function resolve(newValue) { if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) { var then = newValue.then; if (typeof then === 'function') { then.call(newValue, resolve); return; } } state = 'fulfilled'; value = newValue; setTimeout(function () { deferreds.forEach(function (deferred) { handle(deferred); }); }, 0); } fn(resolve); }
初看上述代码时,跟着原文叙述,没能彻底理解其执行流程。通过不断的断点调试,才最终理解。在这一过程中,明白了下面几点
Promise的执行过程可以分为两个阶段,即初始时注册阶段和完成时的resolve阶段
初始时,通过promise.then注册的方法,保存在promise对象的延迟队列。每次调用then方法,返回一个新promis实例,作为链式调用的桥接,这类promise可以乘坐bridge promise
注册的方法执行完,执行resolve时。从当前promise对象延迟队列取出注册的方法继续执行。当注册的方法生成一个新promise实例时,调用then方法注册到对应的延迟队列中;否则依次resolve链式调用中相应的promise实例
将上述过程表达如下
理解上述执行流程,再为上述实现加上错误执行过程和异常处理
function Promise(fn) { var state = 'pending', value = null, deferreds = []; this.then = function (onFulfilled, onRejected) { return new Promise(function (resolve, reject) { handle({ onFulfilled: onFulfilled || null, onRejected: onRejected || null, resolve: resolve, reject: reject }); }); }; function handle(deferred) { if (state === 'pending') { deferreds.push(deferred); return; } var cb = state === 'fulfilled' ? deferred.onFulfilled : deferred.onRejected, ret; if (cb === null) { cb = state === 'fulfilled' ? deferred.resolve : deferred.reject; cb(value); return; } try { ret = cb(value); deferred.resolve(ret); } catch (e) { deferred.reject(e); } } function resolve(newValue) { if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) { var then = newValue.then; if (typeof then === 'function') { then.call(newValue, resolve, reject); return; } } state = 'fulfilled'; value = newValue; finale(); } function reject(reason) { state = 'rejected'; value = reason; finale(); } function finale() { setTimeout(function () { deferreds.forEach(function (deferred) { handle(deferred); }); }, 0); } fn(resolve, reject); }
3.更好的实现
通过阅读剖析 Promise 之基础篇,跟进上述代码执行过程,相信已经可以理解Promise实现原理。在理解过程中,让我思考,上述执行流程到底是哪里不易理解,有没有更好的实现呢?
在上述的实现中,关键方法resolve被定义在Promise中作为内部函数,通过闭包获取promise对象的变量的引用。再将回调函数和resolve方法注册到延迟队列,通过resolve完成了链式的回调。这一过程隐式调用太多,不好理解。
继续阅读其他文章,认为这篇JS Promise的实现原理中有更好的实现。其完整实现可以查看这里。
3.1 代码结构
构造函数定义如下function Promise(resolver) { this._status = 'pending'; this._doneCallbacks = []; this._failCallbacks = []; resolver(resolve, reject); ... }
在 promise 对象中定义了成功回调和失败回调的两个数组,数组中的每一项是通过内部封装的闭包函数调用的结果,也是一个函数。这个函数可以访问到内部调用闭包时传递的 promise 对象,因此通过这种方式也可以访问到我们需要的下一个 promise 以及其关联的成功、失败回调的引用。在then方法中增加闭包调用以及为前一个 promise 对象保存引用。
Promise.prototype.then = function(onResolve, onReject) { var promise = new Promise(function() {}); this._doneCallbacks.push(makeCallback(promise, onResolve, 'resolve')); this._failCallbacks.push(makeCallback(promise, onReject, 'reject')); return promise; }
then方法中调用的makeCallback即上面说到的闭包函数。调用时会把 promise 对象以及相应的回调传递进去,返回一个新的函数。前一个 promise 对象持有返回函数的引用,这样在调用返回函数时,在函数内部就可以访问到 promise 对象以及回调函数了。
function makeCallback(promise, callback, action) { return function promiseCallback(value) { ... }; }
resolve和reject函数的实现相对简单
function resolve(promise, data) { if (promise._status !== 'pending') { return; } promise._status = 'fullfilled'; promise._value = data; run(promise); } function reject(promise, reason) { if (promise._status !== 'pending') { return; } promise._status = 'rejected'; promise._value = reason; run(promise); } function run(promise) { // `then`方法中也会调用,所以此处仍需做一次判断 if (promise._status === 'pending') { return; } var value = promise._value; var callbacks = promise._status === 'fullfilled' ? promise._doneCallbacks : promise._failCallbacks; // Promise需要异步操作 setTimeout(function () { for (var i = 0, len = callbacks.length; i < len; i++) { callbacks[i](value); } }); // 每个promise只能被执行一次。虽然`_doneCallbacks`和`_failCallbacks`用户不应该直接访问, // 但还是可以访问到,保险起见,做清空处理。 promise._doneCallbacks = []; promise._failCallbacks = []; }
3.2 完整实现
除上述基本方法外,文中还实现了常用静态方法如Promise.all,Promise.race,完整代码如下/** * Promise对象的内部状态 * * @type {Object} */ var Status = { PENDING: 'pending', FULLFILLED: 'resolved', REJECTED: 'rejected' }; function empty() {} /** * Promise构造函数 * * @constructor * @param {Function} resolver 此Promise对象管理的任务 */ function Promise(resolver) { // ES6原生的Promise构造函数中,若不通过`new`调用Promise的构造函数,会抛出TypeError异常。此处与其一致 if (!(this instanceof Promise)) { throw new TypeError('TypeError: undefined is not a promise'); } // ES6原生的Promise构造函数中,若无作为函数的resolver参数,会抛出TypeError异常。此处与其一致 if (typeof resolver !== 'function') { throw new TypeError('TypeError: Promise resolver undefined is not a function'); } /** * Promise对象内部的状态,初始为`pending`。状态只能由`pending`到`fullfilled`或`rejected` * * @type {string} */ this._status = Status.PENDING; /** * Promise对象resolved/rejected后拥有的data/reason * * - 此处保存此值是为了当一个Promise对象被resolved或rejected后,继续对其调用`then`添加任务,后续处理仍能获得当前Promise的值 * * @type {Mixed} */ this._value; /** * 当前Promise被resolved/rejected后,需处理的任务 * * - 由于同一个Promise对象可以调用多次`then`方法,以添加多个并行任务,所以此处是一个数组 * * @type {Array.<Function>} */ this._doneCallbacks = []; this._failCallbacks = []; var promise = this; resolver( function (data) { resolve(promise, data); }, function (reason) { reject(promise, reason); } ); } Promise.prototype = { constructor: Promise, /** * Promise的`then`方法 * * @param {Function|Mixed} onResolve 当前Promise对象被resolved后,需处理的任务 * @param {Function|Mixed} onReject 当前Promise对象被rejected后,需处理的任务 * @return {Promise} 返回一个新的Promise对象,用于链式操作 */ then: function (onResolve, onReject) { var promise = new Promise(empty); this._doneCallbacks.push(makeCallback(promise, onResolve, 'resolve')); this._failCallbacks.push(makeCallback(promise, onReject, 'reject')); // 如果在一个已经被fullfilled或rejected的promise上调用then,则需要直接执行通过then注册的回调函数 run(this); return promise; }, /** * Promise的`done`方法 * * @param {Function|Mixed} onResolve 当前Promise对象被resolved后,需处理的任务 * @return {Promise} 返回一个新的Promise对象,用于链式操作 */ done: function (onResolve) { return this.then(onResolve, null); }, /** * Promise的`fail`方法 * * @param {Function|Mixed} onReject 当前Promise对象被rejected后,需处理的任务 * @return {Promise} 返回一个新的Promise对象,用于链式操作 */ fail: function (onReject) { return this.then(null, onReject); }, /** * Promise的`catch`方法 * * @param {Function|Mixed} onFail 当前Promise对象被rejected后,需处理的任务 * @return {Promise} 返回一个新的Promise对象,用于链式操作 */ catch: function (onFail) { return this.then(null, onFail); } }; /** * 创建一个Promise对象,并用给定值resolve它 * * @param {Mixed} value 用于resolve新创建的Promise对象的值 * @return {Promise} 返回一个新的Promise对象,用于链式操作 */ Promise.resolve = function (value) { var promise = new Promise(empty); resolve(promise, value); return promise; }; /** * 创建一个Promise对象,并用给定值reject它 * * @param {Mixed} reason 用于reject新创建的Promise对象的值 * @return {Promise} 返回一个新的Promise对象,用于链式操作 */ Promise.reject = function (reason) { var promise = new Promise(empty); reject(promise, reason); return promise; }; /** * 返回一个promise,这个promise在iterable中的任意一个promise被解决或拒绝后, * 立刻以相同的解决值被解决或以相同的拒绝原因被拒绝 * * @param {Iterable.<Promise|Mixed>} iterable 一组Promise对象或其它值 * @return {Promise} 返回一个新的Promise对象,用于链式操作 */ Promise.race = function (iterable) { if (!iterable || !iterable.hasOwnProperty('length')) { throw new TypeError('TypeError: Parameter `iterable` must be a iterable object'); } var promise = new Promise(empty); for (var i = 0, len = iterable.length; i < len; i++) { var iterate = iterable[i]; if (!(iterate instanceof Promise)) { iterate = Promise.resolve(iterate); } iterate.then(resolveRaceCallback, rejectRaceCallback); } var settled = false; function resolveRaceCallback(data) { if (settled) { return; } settled = true; resolve(promise, data); } function rejectRaceCallback(reason) { if (settled) { return; } settled = true; reject(promise, reason); } }; /** * 返回一个promise,该promise会在iterable参数内的所有promise都被解决后被解决 * * @param {Iterable.<Promise|Mixed>} iterable 一组Promise对象或其它值 * @return {Promise} 返回一个新的Promise对象,用于链式操作 */ Promise.all = function (iterable) { if (!iterable || !iterable.hasOwnProperty('length')) { throw new TypeError('TypeError: Parameter `iterable` must be a iterable object'); } var promise = new Promise(empty); var length = iterable.length; for (var i = 0; i < length; i++) { var iterate = iterable[i]; if (!(iterate instanceof Promise)) { iterate = Promise.resolve(iterate); } iterate.then(makeAllCallback(iterate, i, 'resolve'), makeAllCallback(iterate, i, 'reject')); } var result = []; var count = 0; function makeAllCallback(iterate, index, action) { return function (value) { if (action === 'reject') { reject(promise, value); return; } result[index] = value; if (++count === length) { resolve(promise, result); } } } }; /** * 返回一个Deferred对象,包含一个新创建的Promise对象,以及`resolve`和`reject`方法 * * @return {Deferred} */ Promise.defer = function () { var promise = new Promise(empty); return { promise: promise, resolve: function (data) { resolve(promise, data); }, reject: function (reason) { reject(promise, reason); } }; }; function run(promise) { // `then`方法中也会调用,所以此处仍需做一次判断 if (promise._status === Status.PENDING) { return; } var value = promise._value; var callbacks = promise._status === Status.FULLFILLED ? promise._doneCallbacks : promise._failCallbacks; // Promise需要异步操作 setTimeout(function () { for (var i = 0, len = callbacks.length; i < len; i++) { callbacks[i](value); } }); // 每个promise只能被执行一次。虽然`_doneCallbacks`和`_failCallbacks`用户不应该直接访问, // 但还是可以访问到,保险起见,做清空处理。 promise._doneCallbacks = []; promise._failCallbacks = []; } function resolve(promise, data) { if (promise._status !== Status.PENDING) { return; } promise._status = Status.FULLFILLED; promise._value = data; run(promise); } function reject(promise, reason) { if (promise._status !== Status.PENDING) { return; } promise._status = Status.REJECTED; promise._value = reason; run(promise); } function makeCallback(promise, callback, action) { return function promiseCallback(value) { // 如果传递了callback,则使用前一个promise传递过来的值作为参数调用callback, // 并根据callback的调用结果来处理当前promise if (typeof callback === 'function') { var x; try { x = callback(value); } catch (e) { // 如果调用callback时抛出异常,则直接用此异常对象reject当前promise reject(promise, e); } // 如果callback的返回值是当前promise,为避免造成死循环,需要抛出异常 // 根据Promise+规范,此处应抛出TypeError异常 if (x === promise) { var reason = new TypeError('TypeError: The return value could not be same with the promise'); reject(promise, reason); } // 如果返回值是一个Promise对象,则当返回的Promise对象被resolve/reject后,再resolve/reject当前Promise else if (x instanceof Promise) { x.then( function (data) { resolve(promise, data); }, function (reason) { reject(promise, reason); } ); } else { var then; (function resolveThenable(x) { // 如果返回的是一个Thenable对象(此处逻辑有点坑,参照Promise+的规范实现) if (x && (typeof x === 'object'|| typeof x === 'function')) { try { then = x.then; } catch (e) { reject(promise, e); return; } if (typeof then === 'function') { // 调用Thenable对象的`then`方法时,传递进去的`resolvePromise`和`rejectPromise`方法(及下面的两个匿名方法) // 可能会被重复调用。但Promise+规范规定这两个方法有且只能有其中的一个被调用一次,多次调用将被忽略。 // 此处通过`invoked`来处理重复调用 var invoked = false; try { then.call( x, function (y) { if (invoked) { return; } invoked = true; // 避免死循环 if (y === x) { throw new TypeError('TypeError: The return value could not be same with the previous thenable object'); } // y仍有可能是thenable对象,递归调用 resolveThenable(y); }, function (e) { if (invoked) { return; } invoked = true; reject(promise, e); } ); } catch (e) { // 如果`resolvePromise`和`rejectPromise`方法被调用后,再抛出异常,则忽略异常 // 否则用异常对象reject此Promise对象 if (!invoked) { reject(promise, e); } } } else { resolve(promise, x); } } else { resolve(promise, x); } }(x)); } } // 如果未传递callback,直接用前一个promise传递过来的值resolve/reject当前Promise对象 else { action === 'resolve' ? resolve(promise, value) : reject(promise, value); } }; }
4.最后
相比,剖析 Promise 之基础篇,JS Promise的实现原理文中代码结构要更加清晰,对闭包的调用更为易读。像resolve(),reject(),run(),makeCallback()这几个工具方法,没有直接定义到Promise构造函数中,作为内部函数,产生隐式的闭包调用,而是提出来作为公共方法,通过参数传递方式将promise对象传入方法。使得代码可读性好,容易理解。
同时,在学习的过程中,读到这篇奇舞团翻译的Bluebird
是如何做到比原生实现更快的?
文中提到了Bluebird相对于原生Promise的几个优化点
函数中的对象分配最小化
减小对象体积
可选特性懒重写
其第一点就是避免在Promise构造函数中,直接定义其他内部函数。除开前面提到的可读性问题,避免这样做,可以避免每次创建Promise对象时,同时创建全新的内部函数对象。
参考文章
JavaScript Promise Cookbook中文版
Promise A+ 规范
剖析 Promise 之基础篇
JS Promise的实现原理
Bluebird
是如何做到比原生实现更快的?
剖析Promise内部结构,一步一步实现一个完整的、能通过所有Test case的Promise类
细嗅promise
相关文章推荐
- 深入理解 JavaScript 异步系列(3)—— ES6 中的 Promise
- Promise深入理解
- 深入理解 Promise (上)
- 深入理解 promise(完全转载)
- 深入理解js promise chain
- 深入理解 Promise (中)
- 《深入理解ES6》阅读笔记 --- Promise与异步编程
- 深入理解 Promise 的行为
- 深入理解 Promise (下)
- 【读书笔记】【深入理解ES6】#11-Promise与异步编程
- 深入理解Promise.all
- [转]深入理解 Promise 五部曲:1. 异步问题
- 深入理解ES6里的promise
- 深入理解Promise框架(解决js中的回调地域问题!)
- 深入理解 Promise 五部曲:5. LEGO
- 深入理解 Promise 五部曲:2. 控制权转换问题
- 深入理解javascript之初识promise
- 深入理解JS异步编程三(promise)
- 深入理解 Promise 五部曲:3. 可靠性问题
- 深入理解JavaScript的Promise