我将代码重构为承诺,并构建了一个奇妙的长而平坦的承诺链,由多个.then()回调组成。最后我想返回一些复合值,并需要访问多个中间承诺结果。然而,从序列中间的分辨率值不在最后回调的范围内,我如何访问它们?
function getExample() {
return promiseA(…).then(function(resultA) {
// Some processing
return promiseB(…);
}).then(function(resultB) {
// More processing
return // How do I gain access to resultA here?
});
}
解决方案:
你可以使用'bind'显式地将中间值放在任何后来的'then'函数的作用域中。这是一个很好的解决方案,它不需要改变promise的工作方式,只需要一两行代码来传播这些值,就像已经传播错误一样。
下面是一个完整的例子:
// Get info asynchronously from a server
function pGetServerInfo()
{
// then value: "server info"
} // pGetServerInfo
// Write into a file asynchronously
function pWriteFile(path,string)
{
// no then value
} // pWriteFile
// The heart of the solution: Write formatted info into a log file asynchronously,
// using the pGetServerInfo and pWriteFile operations
function pLogInfo(localInfo)
{
var scope={localInfo:localInfo}; // Create an explicit scope object
var thenFunc=p2.bind(scope); // Create a temporary function with this scope
return (pGetServerInfo().then(thenFunc)); // Do the next 'then' in the chain
} // pLogInfo
// Scope of this 'then' function is {localInfo:localInfo}
function p2(serverInfo)
{
// Do the final 'then' in the chain: Writes "local info, server info"
return pWriteFile('log',this.localInfo+','+serverInfo);
} // p2
该解决方案的调用方式如下:
pLogInfo("local info").then().catch(err);
(注意:这个解决方案的一个更复杂和完整的版本已经测试过了,但没有这个示例版本,所以它可能有一个bug。)
关于承诺,我学到的是只将其用作返回值,尽可能避免引用它们。Async /await语法对此特别实用。今天所有最新的浏览器和节点都支持它:https://caniuse.com/#feat=async-functions,是一个简单的行为,代码就像读取同步代码,忘记回调…
在我确实需要引用承诺的情况下,创建和解决发生在独立/不相关的地方。因此,我宁愿使用人工关联和事件侦听器来解析“远程”承诺,而更喜欢将承诺公开为Deferred,下面的代码在有效的es5中实现了它
/**
* Promise like object that allows to resolve it promise from outside code. Example:
*
```
class Api {
fooReady = new Deferred<Data>()
private knower() {
inOtherMoment(data=>{
this.fooReady.resolve(data)
})
}
}
```
*/
var Deferred = /** @class */ (function () {
function Deferred(callback) {
var instance = this;
this.resolve = null;
this.reject = null;
this.status = 'pending';
this.promise = new Promise(function (resolve, reject) {
instance.resolve = function () { this.status = 'resolved'; resolve.apply(this, arguments); };
instance.reject = function () { this.status = 'rejected'; reject.apply(this, arguments); };
});
if (typeof callback === 'function') {
callback.call(this, this.resolve, this.reject);
}
}
Deferred.prototype.then = function (resolve) {
return this.promise.then(resolve);
};
Deferred.prototype.catch = function (r) {
return this.promise.catch(r);
};
return Deferred;
}());
编译自我的一个typescript项目:
https://github.com/cancerberoSgx/misc-utils-of-mine/blob/2927c2477839f7b36247d054e7e50abe8a41358b/misc-utils-of-mine-generic/src/promise.ts#L31
对于更复杂的情况,我经常使用这些没有测试和输入依赖关系的小工具。P-map已经有用过几次了。我认为他涵盖了大多数用例:
https://github.com/sindresorhus?utf8=%E2%9C%93&tab=repositories&q=promise&type=source&language=
嵌套(和)闭包
使用闭包来维护变量的作用域(在我们的例子中是成功回调函数参数)是自然的JavaScript解决方案。有了promises,我们可以任意嵌套和平化.then()回调函数——它们在语义上是等价的,除了内部的范围不同。
function getExample() {
return promiseA(…).then(function(resultA) {
// some processing
return promiseB(…).then(function(resultB) {
// more processing
return // something using both resultA and resultB;
});
});
}
当然,这是在建造一个压痕金字塔。如果缩进变得太大,您仍然可以应用旧的工具来对抗厄运金字塔:模块化,使用额外的命名函数,并在您不再需要变量时将承诺链压平。
理论上,您总是可以避免超过两层嵌套(通过使所有闭包显式),在实践中使用尽可能多的是合理的。
function getExample() {
// preprocessing
return promiseA(…).then(makeAhandler(…));
}
function makeAhandler(…)
return function(resultA) {
// some processing
return promiseB(…).then(makeBhandler(resultA, …));
};
}
function makeBhandler(resultA, …) {
return function(resultB) {
// more processing
return // anything that uses the variables in scope
};
}
对于这类局部应用程序,您还可以使用helper函数,如_。来自Underscore/lodash或本机.bind()方法的部分,以进一步减少缩进:
function getExample() {
// preprocessing
return promiseA(…).then(handlerA);
}
function handlerA(resultA) {
// some processing
return promiseB(…).then(handlerB.bind(null, resultA));
}
function handlerB(resultA, resultB) {
// more processing
return // anything that uses resultA and resultB
}