承诺具有状态,它们开始时是待定的,可以解决:
这意味着计算成功完成。拒绝表示计算失败。
承诺返回函数不应该抛出,而是应该返回拒绝。从promise返回函数抛出将迫使您同时使用一个}catch{和一个.catch。使用promise API的人不希望抛出promise。如果您不确定异步API在JS中的工作方式,请先看这个答案。
1.DOM加载或其他一次性事件:
因此,创建承诺通常意味着指定它们何时结算——这意味着它们何时转到已完成或已拒绝阶段,以表明数据可用(并且可以使用.then访问)。
使用支持promise构造函数的现代promise实现,如本机ES6 promise:
function load() {
return new Promise(function(resolve, reject) {
window.onload = resolve;
});
}
然后,您可以这样使用由此产生的承诺:
load().then(function() {
// Do things after onload
});
对于支持deferred的库(让我们在这里使用$q作为示例,但我们稍后也将使用jQuery):
function load() {
var d = $q.defer();
window.onload = function() { d.resolve(); };
return d.promise;
}
或者使用类似jQuery的API,钩住发生一次的事件:
function done() {
var d = $.Deferred();
$("#myObject").once("click",function() {
d.resolve();
});
return d.promise();
}
2.普通回调:
这些API很常见,因为在JS中回调很常见。让我们看看onSuccess和onFail的常见情况:
function getUserData(userId, onLoad, onFail) { …
使用支持promise构造函数的现代promise实现,如本机ES6 promise:
function getUserDataAsync(userId) {
return new Promise(function(resolve, reject) {
getUserData(userId, resolve, reject);
});
}
对于支持延迟的库(让我们在这里使用jQuery作为示例,但我们也使用了上面的$q):
function getUserDataAsync(userId) {
var d = $.Deferred();
getUserData(userId, function(res){ d.resolve(res); }, function(err){ d.reject(err); });
return d.promise();
}
jQuery还提供了一个$Deferred(fn)表单,它的优点是允许我们编写一个非常类似于新Promise(fn)格式的表达式,如下所示:
function getUserDataAsync(userId) {
return $.Deferred(function(dfrd) {
getUserData(userId, dfrd.resolve, dfrd.reject);
}).promise();
}
注意:这里我们利用了jQuerydeferred的解析和拒绝方法是“可分离的”这一事实;即,它们绑定到jQuery.Deferred()的实例。并非所有的库都提供此功能。
3.节点样式回调(“nodeback”):
节点样式回调(nodebacks)具有特定的格式,其中回调总是最后一个参数,其第一个参数是错误。让我们先手动承诺:
getStuff("dataParam", function(err, data) { …
To:
function getStuffAsync(param) {
return new Promise(function(resolve, reject) {
getStuff(param, function(err, data) {
if (err !== null) reject(err);
else resolve(data);
});
});
}
使用deferres,您可以执行以下操作(让我们在本例中使用Q,尽管Q现在支持您更喜欢的新语法):
function getStuffAsync(param) {
var d = Q.defer();
getStuff(param, function(err, data) {
if (err !== null) d.reject(err);
else d.resolve(data);
});
return d.promise;
}
一般来说,您不应该手动过多地promise,大多数考虑到Node的promise库以及Node 8+中的原生promise都有一个内置的promise nodeback方法。例如
var getStuffAsync = Promise.promisify(getStuff); // Bluebird
var getStuffAsync = Q.denodeify(getStuff); // Q
var getStuffAsync = util.promisify(getStuff); // Native promises, node only
4.具有节点样式回调的整个库:
这里没有金科玉律,你一个接一个地答应他们。然而,一些promise实现允许您批量执行此操作,例如在Bluebird中,将nodeback API转换为promise API非常简单:
Promise.promisifyAll(API);
或者在Node中使用本机承诺:
const { promisify } = require('util');
const promiseAPI = Object.entries(API).map(([key, v]) => ({key, fn: promisify(v)}))
.reduce((o, p) => Object.assign(o, {[p.key]: p.fn}), {});
笔记:
当然,当你在.then处理程序中时,你不需要承诺。从.then处理程序返回promise将使用该promise的值进行解析或拒绝。从.then处理程序投掷也是一种很好的做法,它会拒绝承诺-这就是著名的承诺投掷安全。在实际的onload情况下,应该使用addEventListener而不是onX。