承诺有状态,它们开始时是挂起状态,可以解决为:
- 已实现意味着计算成功完成。
- 已拒绝意味着计算失败。
返回承诺的函数不应该抛出异常,而应该返回拒绝。从返回承诺的函数中抛出异常将强制您同时使用 catch {}
和 .catch
。使用承诺化API的人们不希望承诺抛出异常。如果您不确定JS中的异步API如何工作,请首先查看此答案。
1. DOM加载或其他一次性事件:
因此,创建承诺通常意味着指定它们何时解决 - 这意味着何时移动到实现或拒绝阶段以表示数据可用(并且可以使用.then
访问)。
借助现代支持Promise
构造函数的承诺实现,例如原生ES6承诺:
function load() {
return new Promise(function(resolve, reject) {
window.onload = resolve;
});
}
您可以像这样使用得到的 Promise:
load().then(function() {
});
有支持延迟执行的库(这里我们用 $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)
形式,它的优点在于允许我们编写一个表达式,非常接近于new Promise(fn)
形式,如下所示:
jQuery也提供了$.Deferred(fn)
形式,这种形式的优势在于可以使我们编写一个表达式,非常接近于new Promise(fn)
形式,具体请参考以下代码:
function getUserDataAsync(userId) {
return $.Deferred(function(dfrd) {
getUserData(userId, dfrd.resolve, dfrd.reject);
}).promise();
}
注意:这里我们利用了jQuery deferred的resolve
和reject
方法是“可分离”的事实;即它们绑定在一个jQuery.Deferred()实例上。并非所有库都提供此功能。
3. Node风格回调("nodeback"):
Node风格回调(nodebacks)具有特定的格式,其中回调始终是最后一个参数,其第一个参数是错误。让我们首先手动将其promisify:
getStuff("dataParam", function(err, data) { …
收件人:
function getStuffAsync(param) {
return new Promise(function(resolve, reject) {
getStuff(param, function(err, data) {
if (err !== null) reject(err);
else resolve(data);
});
});
}
使用deferreds,您可以执行以下操作(让我们在此示例中使用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;
}
通常情况下,您不应该手动过度promisify一些东西,大多数Promise库都是为Node设计的,并且在Node 8+中具有内置的方法用于promisifying回调函数。例如
var getStuffAsync = Promise.promisify(getStuff); // Bluebird
var getStuffAsync = Q.denodeify(getStuff); // Q
var getStuffAsync = util.promisify(getStuff); // Native promises, node only
4. 一个带有Node风格回调的完整库:
这里没有黄金法则,您需要逐个将它们转换为Promise。但是,一些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
处理程序中时,您不需要将事物进行 Promisify。从 .then
处理程序返回一个 promise 将使用该 promise 的值解析或拒绝。从 .then
处理程序抛出异常也是一种良好的实践,将拒绝 promise - 这就是著名的 promise throw safety。
- 在实际的
onload
情况下,您应该使用 addEventListener
而不是 onX
。
new Promise
是否会增加任何显著的开销?我想将所有同步的 Noje.js 函数包装在 Promise 中,以从我的 Node 应用程序中删除所有同步代码,但这是最佳实践吗?换句话说,接受静态参数(例如字符串)并返回计算结果的函数,我应该将其包装在 Promise 中吗?...我在某个地方读到过,在 Nodejs 中不应该有任何同步代码。 - Ronnie Royston