您的位置:首页 > 其它

深入理解Promise

2017-06-04 22:21 302 查看
本文缘起前段时间一朋友换工作时,笔试题中要求手写一个Promise。在工作中虽已大量使用Promise,其原理却没有深入探究过,换做自己,当场也很难手写一个完善的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 之基础篇和JS
Promise的实现原理这两篇文章,个人认为质量较高。本文对原理的理解,也是基于这两篇文章。



借用MDN的图,看看Promise的状态迁移,每个 Promise 存在三个互斥状态:pending、fulfilled、rejected,它们之间的关系是:




2.1 Promise简易实现

最初我是阅读剖析 Promise 之基础篇来学习的,文中初始实现了一个简易版的Promise

function 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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: