用Promises
书写更好的JavaScript
使用
Promises
更易阅读
$.getJSON('http://hipsterjesus.com/api/', function(data) {
$('body').append(data.text);
});
如果你用过jQuery
,你就会知道这段代码发送了一个GET
请求,并且期望得到一个JSON
响应。你也可以这样写:
var promise = $.getJSON('http://hipsterjesus.com/api/');
promise.done(function(data) {
$('body').append(data.text);
});
当失败的时候:
var promise = $.getJSON('http://hipsterjesus.com/api/');
promise.done(function(data) {
$('body').append(data.text);
});
promise.fail(function() {
$('body').append('<p>Oh no, something went wrong!</p>');
});
大多数人都不写promise
变量:
$.getJSON('http://hipsterjesus.com/api/')
.done(function(data) {
$('body').append(data.text);
})
.fail(function() {
$('body').append('<p>Oh no, something went wrong!</p>');
});
jQuery
也有一个always
方法,无论成功还是失败都会执行:
$.getJSON('http://hipsterjesus.com/api/')
.done(function(data) {
$('body').append(data.text);
})
.fail(function() {
$('body').append('<p>Oh no, something went wrong!</p>');
})
.always(function() {
$('body').append('<p>I promise this will always be added!.</p>');
});
更好的API
var hipsterJesus = {
html: function() {
return $.getJSON('http://hipsterjesus.com/api/').then(function(data) {
return data.text;
});
}
};
我们返回的是一个promise
对象,这样的话,任何调用这个方法的对象都可以再注册一个done
方法到上面。then
方法允许我们修改promise
的结果,然后传递给下一个链条:
hipsterJesus.html().done(function(html) {
$("body").append(html);
});
Chaining 调用链
var hipsterJesus = {
html: function() {
return $.getJSON('http://hipsterjesus.com/api/').then(function(data) {
return data.text;
});
},
paragraphs: function() {
return this.html().then(function(html) {
return html.replace(/<[^>]+>/g, "").split("");
});
}
};
Multiple calls 多个调用
也许promises
最炫的就是可以结合多个API
调用了,有时候你可能会遇到这样的情况:
var firstData = null;
var secondData = null;
var responseCallback = function() {
if (!firstData || !secondData)
return;
// do something
}
$.get("http://example.com/first", function(data) {
firstData = data;
responseCallback();
});
$.get("http://example.com/second", function(data) {
secondData = data;
responseCallback();
});
使用promises
的话,这种情况变的容易了许多:
var firstPromise = $.get("http://example.com/first");
var secondPromise = $.get("http://example.com/second");
$.when(firstPromise, secondPromise).done(function(firstData, secondData) {
// do something
});
实现
状态机
因为promise
其实就是一个状态机,我们需要定义一些我们随后可能会使用的状态:
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
var handlers = [];
}
Transitions 过渡
现在,让我们实现两个关键的过渡,它们可以发生,完成和拒绝:
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;
}
}
这让我们拥有了一个比较低级的过渡,但是让我们考虑的更多一些,站在一个更高的层级的称为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);
}
}
}
注意,resolve
是怎样接受一个promise
或者一个plain value
的,如果它是一个promise
,那么就等待它完成。一个promise
必须不能通过另外一个proomise
来完成,所以它就是这个我们想要暴露的resolve
,而不是内部的fulfill
函数。我们将会使用一系列工具方法:
/**
* Check if a value is a Promise and, if it is,
* return the `then` method of that promise.
*
* @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 构造
我们现在已经拥有了一个比较完整的内部状态机,但是我们还没有对外暴露任何用户解析promise
或者observing
它的方法。让我们来加入它:
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);
}
如你所见,因为我们有其他untrusted resolver
,所以我们又重用了方法doResolve
。fn
可以同时调用resolve
和reject
多次,甚至抛出异常。我们应该确保promise
只被解析或者拒绝一次,然后就不要再被transition
到一个新的状态。
Observing (via .done)
我们现在拥有了一个完整的状态机,但是我们仍然没有办法来观察它的任何改变。我们的终极目标是实现.then
,.done
的语义比.then
要简单一些,所以让我们首先来实现它。
我们现在的目标是实现promise.done(onFulfilled, onRejected)
:
- 只有哦一个
onFulfilled
或者onRejected
被调用 - 仅被调用一次
- 直到下一次
tick
才会被调用 - 不管
promise
是在我们调用.done
之前还是之后被解析的,都应该调用
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;
handlers.forEach(handle);
handlers = null;
}
function reject(error) {
state = REJECTED;
value = error;
handlers.forEach(handle);
handlers = null;
}
function resolve(result) {
try {
var then = getThen(result);
if (then) {
doResolve(then.bind(result), resolve, reject)
return
}
fulfill(result);
} catch (e) {
reject(e);
}
}
function handle(handler) {
if (state === PENDING) {
handlers.push(handler);
} else {
if (state === FULFILLED &&
typeof handler.onFulfilled === 'function') {
handler.onFulfilled(value);
}
if (state === REJECTED &&
typeof handler.onRejected === 'function') {
handler.onRejected(value);
}
}
}
this.done = function (onFulfilled, onRejected) {
// ensure we are always asynchronous
setTimeout(function () {
handle({
onFulfilled: onFulfilled,
onRejected: onRejected
});
}, 0);
}
doResolve(fn, resolve, reject);
}
Observing (via .then)
现在我们实现了.done
方法,我们可以很容易地实现.then
方法做一些相同的事情,但是在这个过程中生成一个新的Promise
:
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);
}
});
});
}