Introduction
This article was originally written as an answer on Stack Overflow. The hope is that by seeing how you would go about implementing Promise
in JavaScript, you may gain a better understanding of how promises behave.
介绍
这篇文章起初是在Stack Overflow网站里的一个回答。希望大家看完在 JavaScript 去实现 Promise 这个过程之后,可以更好地理解 promise。
State Machine
Since a promise is just a state machine, we should start by considering the state information we will need later.
既然 promise 仅仅是一个 state machine, 我们应该先考虑待会儿要用到的 state 信息。
var PENDING = 0;
var FULFILLED = 1;
var REJECTED = 2;
function Promise() {
// store state which can be PENDING, FULFILLED or REJECTED
var state = PENDING;
// store value or error once FULFILLED or REJECTED
var value = null;
// store sucess & failure handlers attached by calling .then or .done
// attached 附上的
var handlers = [];
}
Transitions
Next, lets consider the two key transitions that can occur, fulfilling and rejecting:
接下来,我们就来考虑这两个关键的转换, funfilling 和 rejecting:
var PENDING = 0;
var FULFILLED = 1;
var REJECTED = 2;
function Promise() {
// store state which can be PENDING, FULFILLED or REJECTED
var state = PENDING;
// store value once FULFILLED or REJECTED
var value = null;
// store sucess & failure handlers
var handlers = [];
function fulfill(result) {
state = FULFILLED;
value = result;
}
function reject(error) {
state = REJECTED;
value = error;
}
}
That gives us the basic low level transitions, but lets consider an extra, higher-level transition called resolve
.
它给了我们最基本的转换,但让我们考虑一个其他的,更高级的方法:resolve。
var PENDING = 0;
var FULFILLED = 1;
var REJECTED = 2;
function Promise() {
// store state which can be PENDING, FULFILLED or REJECTED
var state = PENDING;
// store value once FULFILLED or REJECTED
var value = null;
// store sucess & failure handlers
var handlers = [];
function fulfill(result) {
state = FULFILLED;
value = result;
}
function reject(error) {
state = REJECTED;
value = error;
}
function resolve(result) {
try {
var then = getThen(result);
if (then) {
doResolve(then.bind(result), resolve, reject)
return
}
fulfill(result);
} catch (e) {
reject(e);
}
}
}
Note how resolve
accepts either a promise or a plain value and if it’s a promise, waits for it to complete. A promise must never be fulfilled with another promise, so it is this resolve
function that we will expose, rather than the internal fulfill
. We’ve used a couple of helper methods, so lets define those:
展示了如何 resolve 接收一个 promise 或者一个值,如果是 promise 的话,就等待它执行完在接收。 promise 必须没有被另一个promise fulfilled。所以我们才暴露这个 resolve 方法,而不是内部的fulfill。我们已经用了一些helper方法, 下面就是这些方法。
/**
* Check if a value is a Promise and, if it is,
* return the `then` method of that promise.
* 检查是否是 Promise 如果是的话,就返回那个 promise 的 then 方法。
*
* @param {Promise|Any} value
* @return {Function|Null}
*/
function getThen(value) {
var t = typeof value;
if (value && (t === 'object' || t === 'function')) {
var then = value.then;
if (typeof then === 'function') {
return then;
}
}
return null;
}
/**
* Take a potentially misbehaving resolver function and make sure
* onFulfilled and onRejected are only called once.
*
* Makes no guarantees about asynchrony.
*
* @param {Function} fn A resolver function that may not be trusted
* @param {Function} onFulfilled
* @param {Function} onRejected
*/
function doResolve(fn, onFulfilled, onRejected) {
var done = false;
try {
fn(function (value) {
if (done) return
done = true
onFulfilled(value)
}, function (reason) {
if (done) return
done = true
onRejected(reason)
})
} catch (ex) {
if (done) return
done = true
onRejected(ex)
}
}
Constructing
We now have the completed internal state machine, but we have yet to expose either a method of resolving the promise or of observing it. Lets start by adding a way of resolving the promise.
我们已经完成了state machine,但是我们还没暴露一个resolving方法。让我们开始添加这个方法吧。
var PENDING = 0;
var FULFILLED = 1;
var REJECTED = 2;
function Promise(fn) {
// store state which can be PENDING, FULFILLED or REJECTED
var state = PENDING;
// store value once FULFILLED or REJECTED
var value = null;
// store sucess & failure handlers
var handlers = [];
function fulfill(result) {
state = FULFILLED;
value = result;
}
function reject(error) {
state = REJECTED;
value = error;
}
function resolve(result) {
try {
var then = getThen(result);
if (then) {
doResolve(then.bind(result), resolve, reject)
return
}
fulfill(result);
} catch (e) {
reject(e);
}
}
doResolve(fn, resolve, reject);
}
As you can see, we re-use doResolve
because we have another untrusted resolver. The fn is allowed to call both resolve
and reject
multiple times, and even throw exceptions. It is up to us to ensure that the promise is only resolved or rejected once, and then never transitions into a different state ever again.
就如你们看到的,我们复用了 doResolve 这个方法。因为我们有另外一个不确定的 resolver 。fn 方法允许调用 resolve 和 reject 方法很多次,甚至会抛出异常。 这取决于我们去保证 promise 只是被 resolve 或者 reject 一次,然后再也不会改变它的 state。
Observing (via .done)
We now have a completed state machine, but we still have no way to observe any changes to it. Our ultimate goal is to implement .then
, but the semantics of .done
are much simpler so lets implement that first.
Our goal here is to implement promise.done(onFulfilled, onRejected)
such that:
- only one of
onFulfilled
oronRejected
is called - it is only called once
- it is never called until the next tick (i.e. after the
.done
method has returned) - it is called regardless of whether the promise is resolved before or after we call
.done
我们现在已经完成了 state machine, 但是我们还是没有方法去观察它的任何改变。我们最终的目标就是去实现 .then ,我们先实现它的话,那么 .done 将会更加容易实现。
我们在这里的目标就是去实现 promise.done(onFulfilled, onRejected)
像下面这样:
- onFulfilled 或者 onRejected 只能其中一个被调用
- 只能被调用一次
- 直到下一个标记才会被调用(比日说在 .done 方法被 return 了)
- 调用的时候,不管这个 promise 是否在我们调用 .done 之前或者之后被 resolved
this.then = function (onFulfilled, onRejected) {
var self = this;
return new Promise(function (resolve, reject) {
return self.done(function (result) {
if (typeof onFulfilled === 'function') {
try {
return resolve(onFulfilled(result));
} catch (ex) {
return reject(ex);
}
} else {
return resolve(result);
}
}, function (error) {
if (typeof onRejected === 'function') {
try {
return resolve(onRejected(error));
} catch (ex) {
return reject(ex);
}
} else {
return reject(error);
}
});
});
}
Further Reading
- then/promise implements Promise in a very similar way.
- kriskowal/q is a very different implementation of promises and comes with a very nice walkthrough of the design principals behind it.
- petkaantonov/bluebird is a promise implementation that was designed exclusively for performance (along with its own esoteric helper methods). The Optimization Killers Wiki page is extremely useful for picking up tips.
- Stack Overflow is the original source of this article.