Promise -- 承诺
来源:互联网 发布:aplusvable知乎 编辑:程序博客网 时间:2024/05/17 06:05
1. 链式使用 Promise
现在已经准备好深入 Promise 特性了。首先探索一下如何链式使用 Promsie 。
方法调用的结果:
P.then(onFulfilled, onRejected)
是一个新的 Promise Q 。这意味着你可以通过在 Q 上调用 then() 来维持基于 Promise 的控制流:
• 如果 onFulfilled 或 onRejected 成功返回,则 Q 变为 fulfilled 状态,相应的结果值就是前面函数的返回值。
• 如果 onFulfilled 或 onRejected 抛出异常,则 Q 变为 rejected 状态。
1. 用普通的值将 Q 置为 fulfilled 状态
如果用一个普通的值将 then() 返回的 Promise Q 置为 fulfilled 状态,可以追加一个 then() 来得到那个值:
asyncFunc().then(function (value1) { return 123;}).then(function (value2) { console.log(value2); // 123});
2. 用带有 then 方法的对象将 Q 置为 fulfilled 状态
也可以用带有 then 方法的对象 R 将 then() 返回的 Promise Q 置为 fulfilled 状态。可 then 化( thenable )实体就是任何一个有 Promise 风格方法 then() 的对象。因此, Promise 是可 then 化的。用 R 置为 fulfilled 状态(例如在 onFulfilled 中返回 R )意味着 R 被插在 Q “后面”: R 的稳定消息会转发给 Q 的 onFulfilled 和 onRejected 回调。在某种程度上, Q 变成了 R 。
这种机制的主要用途就是扁平化内嵌的 then() 调用,就像下面的例子:
asyncFunc1().then(function (value1) { asyncFunc2() .then(function (value2) { ••• });})扁平化的版本看起来像这样:asyncFunc1().then(function (value1) { return asyncFunc2();}).then(function (value2) { •••})
3. 从 onRejected 将 Q 置为 fulfilled 状态
在错误处理器中返回的内容变成了一个成功完成的值(而不是被驳回的值)。这使得可以在执行失败的时候指定默认值:
retrieveFileName().catch(function () { // Something went wrong, use a default value return 'Untitled.txt';}).then(function (fileName) { •••});
4. 通过抛出异常将 Q 置为 rejected 状态
在 then() 的两个回调参数中抛出的异常会传递给下一个错误处理器:
asyncFunc().then(function (value) { throw new Error();}).catch(function (reason) { // Handle error here});
5. 执行者中的异常
在执行者( new Promise() 的回调)中抛出的异常传递给该执行者负责管理的 Promise :
new Promise(function (resolve, reject) { throw new Error();}).catch(function (err) { // Handle error here});
6. 链式错误
可以有一个或多个 then() 方法调用,并且这些方法调用中都没有传递错误处理器。此时错误就会一直往下传递,直到遇见一个错误处理器。
asyncFunc1().then(asyncFunc2).then(asyncFunc3).catch(function (reason) { // Something went wrong above});
2. 组合
本节讲述了如何将已有的 Promise 组合起来创建新的 Promise 。我们已经遇到了一种组合 Promise 的方法:通过 then() 依次链式连接。 Promise.all() 和 Promise.race() 提供了另外的组合方式。
1. map() 与 Promise.all() 结合
关于 Promise 一件很好的事情就是很多同步的工具仍然有效,因为基于 Promise 的函数有返回结果。例如,可以使用数组方法 map() :
let fileUrls = [ 'http://example.com/file1.txt', 'http://example.com/file2.txt'];let promisedTexts = fileUrls.map(httpGet);
promisedTexts 是一组 Promise 。 Promise.all() 用一个 Promise 数组(可 then 化的和其它值都通过 Promise.resolve() 被转换成了 Promise )作为参数,一旦所有的 Promise 都成功完成了,它就变为 fulfilled 状态,返回结果是这组 Promise 的返回结果的一个数组:
Promise.all(promisedTexts).then(texts => { for (let text of texts) { console.log(text); }}).catch(reason => { // Receives first rejection among the Promises});
2. 利用 Promise.race() 实现超时
Promise.race() 使用一个 Promise 数组(可 then 化的和其它值都通过 Promise.resolve() 被转换成了 Promise )作为参数,然后返回一个 Promise P 。
传入的 Promise 数组中只要有一个 Promise 变为稳定状态,就会将稳定状态信息传递给返回的那个 Promise P 。
让我们使用 Promise.race() 实现超时:
Promise.race([ httpGet('http://example.com/file.txt'), delay(5000).then(function () { throw new Error('Timed out') });]).then(function (text) { ••• }).catch(function (reason) { ••• });
3. Promise 的优缺点
1. 优点
1.1 统一异步 API
Promise 的一个重要优点是它将逐渐被用作浏览器的异步 API ,统一现在各种各样的 API ,以及不兼容的模式和手法。让我们看两个即将到来的基于 Promise 的 API 。
fetch API 是基于 Promise 的,用于处理 XMLHttpRequest 的一种方式:
fetch(url).then(request => request.text()).then(str => •••)
在实际请求中, fetch() 返回一个 Promise ; text() 也返回一个 Promise ,用于将响应内容转换成字符串。
ES6 中可编程地动态引入模块也是基于 Promise 的:
System.import('some_module.js').then(some_module => { •••})
1.2 Promise 与事件对比
和事件相比较, Promise 更适合处理一次性的结果。
在结果计算出来之前或之后注册回调函数都是可以的,都可以拿到正确的值。
Promise 的这个优点很自然。但是,不能使用 Promise 处理多次触发的事件。链式处理是 Promise 的又一优点,但是事件却不能这样链式处理。
1.3 Promise 与回调对比
和回调函数比较, Promise 有更干净的函数(或者方法)签名。回调函数的场景,主函数既有输入参数,又有输出参数:
// name 和 opt 是输入参数, (err, string | Buffer) => void 是输出参数
fs.readFile(name, opts?, (err, string | Buffer) => void)
Promise 的场景,所有的参数都是输入参数:
readFilePromisified(name, opts?) : Promise
4. Promise 内部机制
本节中,我们将会从一个不一样的角度来学习 Promise :我们将会简单实现 Promise ,而不是学习如何使用 Promise API 。这种不同的视角很大程度地帮助我理解了 Promise 。
此处 Promise 的实现叫做 DemoPromise 。为了理解起来更容易,并没有完全符合 ES6 的 Promise API 。但是仍然足以说明实际的 Promise 实现会遇到什么挑战。
DemoPromise 是有三个原型方法的类:
• DemoPromise.prototype.resolve(value)
• DemoPromise.prototype.reject(reason)
• DemoPromise.prototype.then(onFulfilled, onRejected)
也就是说, resolve 和 reject 是方法(比较传递给构造函数 Promise 的回调函数的参数)。
4.1 一个独立的 Promise
第一个实现是一个独立的 Promise ,只有最少的功能:
• 可以创建 Promise
• 可以通过( resolve )或者拒绝( reject )一个 Promise ,并且只能做一次。
• 可以通过 then() 注册应答器(回调函数)。这一定要与 Promise 是否已经稳定无关。
o 该方法暂不支持链式使用 - 不返回任何东西。
下面是如何使用第一个版本的 Promise :
let dp = new DemoPromise();
dp.resolve(‘abc’);
dp.then(function (value) {
console.log(value); // abc
});
下图说明了我们的第一个 DemoPromise 是如何工作的:
1. DemoPromise.prototype.then()
首先检查 then() 。该方法处理两种情形:
• 如果 Promise 还处于 pending 状态,则会将 onFulfilled 和 onRejected 的调用存储在队列中,当 Promise 稳定下来的时候就使用。
• 如果 Promise 已经处于 fulfilled 状态或者 rejected 状态, onFulfilled 或者 onRejected 可能会被立刻调用。
then(onFulfilled, onRejected) { let self = this; let fulfilledTask = function () { onFulfilled(self.promiseResult); }; let rejectedTask = function () { onRejected(self.promiseResult); }; switch (this.promiseState) { case 'pending': this.fulfillReactions.push(fulfilledTask); this.rejectReactions.push(rejectedTask); break; case 'fulfilled': addToTaskQueue(fulfilledTask); break; case 'rejected': addToTaskQueue(rejectedTask); break; }}
上面的代码片段使用了下面的辅助函数:
function addToTaskQueue(task) { setTimeout(task, 0);}
2. DemoPromise.prototype.resolve()
resolve() 像下面这样工作:如果 Promise 已经稳定了,什么事也不做(确保 Promise 只能变为稳定状态一次)。
否则, Promise 的状态变为 fulfilled ,结果缓存在 this.promiseResult 中。接下来,触发所有之前存放的 fulfilled 状态相关的回调函数。
resolve(value) { if (this.promiseState !== 'pending') return; this.promiseState = 'fulfilled'; this.promiseResult = value; this._clearAndEnqueueReactions(this.fulfillReactions); return this; // enable chaining}_clearAndEnqueueReactions(reactions) { this.fulfillReactions = undefined; this.rejectReactions = undefined; reactions.map(addToTaskQueue);}reject() 类似于 resolve() 。
3. 链式
接下来要实现的特性是链式:
• then() 返回一个 Promise ,它通过 onFulfilled 或 onRejected 的返回来变成 resolved 状态。
• 如果省略 onFulfiled 或 onRejected ,那么不管接收到什么,都会传给 then() 返回的 Promise 。
很明显,只需要修改 then() :
then(onFulfilled, onRejected) { let returnValue = new Promise(); // (A) let self = this; let fulfilledTask; if (typeof onFulfilled === 'function') { fulfilledTask = function () { let r = onFulfilled(self.promiseResult); returnValue.resolve(r); // (B) }; } else { fulfilledTask = function () { returnValue.resolve(self.promiseResult); // (C) }; } let rejectedTask; if (typeof onRejected === 'function') { rejectedTask = function () { let r = onRejected(self.promiseResult); returnValue.resolve(r); // (D) }; } else { rejectedTask = function () { // `onRejected` has not been provided // => we must pass on the rejection returnValue.reject(self.promiseResult); // (E) }; } ••• return returnValue; // (F)}
then() 创建并返回了一个新的 Promise (行 A 和 F), fulfilledTask 和 rejectedTask 被分别设置:在稳定之后……
• onFulfilled 的返回值传入 returnValue 的 resolve 方法(行 B )。
o 如果没有传入 onFulfilled ,就将当前已经完成的值传入 returnValue 的 resolve 方法(行 C )。
• onRejected 的返回值传入 returnValue 的 resolve 方法(不是 reject 方法!)(行 D )。
o 如果没有传入 onRejected ,就将当前已经完成的值传入 returnValue 的 reject 方法(行 E )。
4. Promise 状态的更多细节
实现链式特性之后, Promise 的状态更加复杂了(正如 ECMAScript 6 规范的25.4节描述的那样):
如果你仅使用 Promise ,通常你可以采用一种简单的方式,忽略掉锁定。最重要的状态相关的概念仍然是“稳定( settledness )”:一个 Promise 在变为 fulfilled 状态或 rejected 状态,就稳定了。
在 Promise 稳定之后,就不再会变化了(状态和最终的值都不会变化)。
如果你想实现 Promise ,那么“解析( resolving )”也很重要,并且现在很难理解:
• 直观地,“ resolved ”的意思是“不能再被(直接地)解析了”。如果一个 Promise 稳定了或者锁定了,那么就变成了 resolved 状态。引用规范里面的话:“一个未解析( unresolved )的 Promise 总是处于 pending 状态。一个解析( resolved )的 Promise 可能是 pending 状态,也可能是 fulfilled 或 rejected 状态”。
• 解析( resolving )不一定会使 Promise 变为稳定状态:你可以用另外一个总是处于 pending 状态的 Promise 来解析某个 Promise 。
• 解析( resolving )现在包括了拒绝( rejecting )(即更一般化了):你可以通过一个处于 rejected 状态的 Promise 来解析某个 Promise ,从而使其也变为 rejected 状态。
5. 异常
最后一个特性,我们希望在用户代码抛出异常的时候进入拒绝( rejection )回调函数。此处,“用户代码”指的是 then() 的两个回调参数。
下面的代码展示了我们如何将 onFulfilled 中抛出的异常转移到拒绝回调函数中 - 通过在行 A 用 try-catch 包裹 onFulfilled 的调用。
then(onFulfilled, onRejected) { ••• let fulfilledTask; if (typeof onFulfilled === 'function') { fulfilledTask = function () { try { let r = onFulfilled(self.promiseResult); // (A) returnValue.resolve(r); } catch (e) { returnValue.reject(e); } }; } else { fulfilledTask = function () { returnValue.resolve(self.promiseResult); }; } •••}
1. 启发性的构造器模式
如果我们想将 DemoPromise 变成一个实际可用的 Promise 实现,还需要实现启发性的构造器模式[5]: ES6 的 Promise 不通过方法来解析( resolve )和拒绝( reject ),而是通过传递给执行器( executor )的函数,即构造器的回调参数。
如果执行器抛出异常,那么对应的 Promise 就会变为 rejected 状态。
Promise 附加方法
本节介绍两个很有用的方法,这两个方法可以很容易的添加到 ES6 的 Promise 上面去。很多更全面的 Promise 库都有这两个方法。
1. 扁平化( Flattening )
扁平化主要让链式调用更加方便:通常情况下,将响应器( reaction )返回的值传给下一个 then() 。如果返回一个 Promise ,并且内部并没有做相应的“包装”,这种情形是非常美好的,就像下面的例子:
asyncFunc1().then(function (value1) { return asyncFunc2(); // (A)}).then(function (value2) { // value2 is fulfillment value of asyncFunc2() Promise console.log(value2);});
在行 A 返回一个 Promise ,没必要将下一个 then() 内嵌到当前方法中,可以在当前方法的返回值上调用 then() 。所以:没有内嵌的 then() ,所有内容保持扁平。
通过 resolve() 方法实现这种扁平功能:
• 用 Promise Q 来解析 Promise P 意味着 Q 的稳定情况会转发给 P 。
• P 被“锁在了” Q 上:不能再手动变为 resolved 状态(同样也不能变为 rejected 状态)。它的状态和结果总是和 Q 保持一致。
如果让 Q 变成 thenable 对象(而不仅仅是一个 Promise ),那么就可以让扁平化更加通用,
为了实现锁定,引入一个新的布尔标志 this.alreadyResolved 。一旦该值变为 true , this 就被锁定了,不能再被解析( resolve )了。注意此时 this 可能还处于 pending 状态,因为它的状态现在和锁定到的 Promise 一致。
resolve(value) { if (this.alreadyResolved) return; this.alreadyResolved = true; this._doResolve(value); return this; // enable chaining}
实际的解析过程现在发生在私有的方法 _doResolve() 中:
_doResolve(value) { let self = this; // Is `value` a thenable? if (typeof value === 'object' && value !== null && 'then' in value) { // Forward fulfillments and rejections from `value` to `this`. // Added as a task (vs. done immediately) to preserve async semantics. addToTaskQueue(function () { // (A) value.then( function onFulfilled(result) { self._doResolve(result); }, function onRejected(error) { self._doReject(error); }); }); } else { this.promiseState = 'fulfilled'; this.promiseResult = value; this._clearAndEnqueueReactions(this.fulfillReactions); }}
在行 A 处理了扁平化:如果 value 是 fulfilled 状态,我们希望 self 也是 fulfilled 状态;如果 value 是 rejected 状态,我们希望 self 也是 rejected 状态。通过私有方法 _doResolve 和 _doReject 不停转发,绕过alreadyResolved 标志。
2. done()
当你链接若干个 Promise 方法调用的时候,有一不小心忘记处理某些错误的风险。例如:
function doSomething() { asyncFunc() .then(f1) .catch(r1) .then(f2); // (A)}
如果在行 A 的 then() 触发了拒绝( rejection ),那么这个错误将不会被处理。 Promise 库 Q 提供了一个方法 done() ,被用作链式调用的最后一个调用元素。 done() 要么替换掉最后一个 then() (此时有一到两个参数):
function doSomething() { asyncFunc() .then(f1) .catch(r1) .done(f2);}
要么放在最后一个 then() 的后面(此时没有参数):
function doSomething() { asyncFunc() .then(f1) .catch(r1) .then(f2) .done();}
Q 的文档:
done 和 then 使用的黄金规则就是:要么返回 Promise 给其它地方,要么调用 done 结束当前 Promise 链(如果这条链在当前位置结束的话)。用 catch 来结束 Promise 是不完美的,因为 catch 的处理器自身可能会抛出错误。
下面就是在 ECMAScript 6 中实现 done() 的方式:
Promise.prototype.done = function (onFulfilled, onRejected) { this.then(onFulfilled, onRejected) .catch(function (reason) { // Throw an exception globally setTimeout(() => { throw reason }, 0); });};
虽然 done 的功能很有用,但是还是没有添加到 ECMAScript 6 中去,因为这种检查可以通过引擎自动实现(正如在调试 Promise 的那一节看到的一样)。
3. finally()
有时你想执行一些操作而不管是否发生了错误。例如,在操作完一个资源之后做一些清理工作。这就是 Promise 方法 finally 适用的场景,它和异常处理中的 finally 子句很相似。它的回调函数不接收参数,但是不管是 rejected 还是 resolved 状态都会被调用。
createResource(•••).then(function (value1) { // Use resource}).then(function (value2) { // Use resource}).finally(function () { // Clean up});
下面是 Domenic Denicola 提议的实现 finally() 的方式:
Promise.prototype.finally = function (callback) { let P = this.constructor; // We don’t invoke the callback in here, // because we want then() to handle its exceptions return this.then( // Callback fulfills => continue with receiver’s fulfillment or rejection // Callback rejects => pass on that rejection (then() has no 2nd paramet\er!) value => P.resolve(callback()).then(() => value), reason => P.resolve(callback()).then(() => { throw reason }) );};
回调函数决定了接收器( this )的稳定状态处理的方式:
• 如果回调函数抛出异常或者返回一个 rejected 状态的 Promise ,则会变成拒绝值( rejection value )。
• 否则,接收器的稳定状态( fulfilled 或者 rejected )变成了 finally() 返回的 Promise 的稳定状态。在某种程度上,我们把 finally 从方法链上脱离出来了。
示例1(作者 Jake Archibald )使用 finally() 隐藏一个 spinner 。简单的版本:
showSpinner();fetchGalleryData().then(data => updateGallery(data)).catch(showNoDataError).finally(hideSpinner);
示例2(作者 Kris Kowal )使用 finally() 拆分一个测试:
let HTTP = require("q-io/http");let server = HTTP.Server(app);return server.listen(0).then(function () { // run test}).finally(server.stop);
- Promise -- 承诺
- 给你一个承诺-----promise
- 承诺模式(promise)
- [C++11 并发编程] 15 承诺promise
- Promise(承诺模式) 定义承诺,取结果,执行回调,返回新承诺 (定义承诺-取结果-返回值作为新承诺结果)
- 给你一个承诺 - 玩转 AngularJS 的 Promise
- 多线程设计模式——Promise(承诺)模式
- 给你一个承诺 - 玩转 AngularJS 的 Promise
- ionic2页面回传值,关于Typescript的Promise承诺
- 给你一个承诺,玩转angularjs的Promise
- 承诺
- 承诺
- 承诺
- 承诺
- 承诺
- 《承诺》
- 承诺
- 承诺
- G
- 考试座位号(长整型输出格式
- vijos1025 小飞侠的游园方案(01背包模板)
- 访问者模式 | Visitor Pattern
- 数组方法和字符串方法对比
- Promise -- 承诺
- nginx平滑升级添加ssl实现站内https
- [py]Django 提供的 QuerySet API操作db
- python中的map()函数
- 如何使用apidoc来自动更新API文档
- ubuntu16切换hosts软件安装
- JVM---虚拟机类加载机制与类加载器
- nyoj1057 寻找最大数(三)
- POJ 3268 Silver Cow Party(最短路径之迪杰斯特拉算法)