如何访问.then()链中的先前的诺言结果?

我已经将我的代码重组为promises,并建立了一个精彩的长期承诺链,其中包含多个.then()回调。最后,我想返回一些复合值,并且需要访问多个中间promise结果但是,序列中间的分辨率值不在上次回调的范围内,如何访问它们?

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?
    });
}
逆天前端2020/03/11 20:27:14
function getExample() {
    var retA, retB;
    return promiseA(…).then(function(resultA) {
        retA = resultA;
        // Some processing
        return promiseB(…);
    }).then(function(resultB) {
        // More processing
        //retA is value of promiseA
        return // How do I gain access to resultA here?
    });
}

简单的方法:D

樱Eva2020/03/11 20:27:14

使用bluebird时,可以使用.bind方法在promise链***享变量:

somethingAsync().bind({})
.spread(function (aValue, bValue) {
    this.aValue = aValue;
    this.bValue = bValue;
    return somethingElseAsync(aValue, bValue);
})
.then(function (cValue) {
    return this.aValue + this.bValue + cValue;
});

请检查此链接以获取更多信息:

http://bluebirdjs.com/docs/api/promise.bind.html

神无村村2020/03/11 20:27:14

另一个答案,使用babel-node版本<6

使用 async - await

npm install -g babel@5.6.14

example.js:

async function getExample(){

  let response = await returnPromise();

  let response2 = await returnPromise2();

  console.log(response, response2)

}

getExample()

然后,运行babel-node example.js并瞧!

神无村村2020/03/11 20:27:14

我不会在自己的代码中使用此模式,因为我不太喜欢使用全局变量。但是,在紧急情况下它将起作用。

用户是一个承诺的猫鼬模型。

var globalVar = '';

User.findAsync({}).then(function(users){
  globalVar = users;
}).then(function(){
  console.log(globalVar);
});
神无村村2020/03/11 20:27:14

节点7.4现在支持带有和声标志的异步/等待调用。

尝试这个:

async function getExample(){

  let response = await returnPromise();

  let response2 = await returnPromise2();

  console.log(response, response2)

}

getExample()

并使用以下命令运行文件:

node --harmony-async-await getExample.js

可以简单!

Eva理查德2020/03/11 20:27:14

嵌套(和)闭包

使用闭包来维护变量的范围(在我们的示例中为成功回调函数参数)是自然的JavaScript解决方案。使用promise,我们可以任意嵌套和展平 .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
    };
}

您还可以使用辅助功能对于这种局部的应用,如_.partial下划线 / 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
}
A小卤蛋Pro2020/03/11 20:27:14

对“可变上下文状态”的苛刻要求较低

使用本地范围的对象来收集承诺链中的中间结果是解决您提出的问题的一种合理方法。考虑以下代码段:

function getExample(){
    //locally scoped
    const results = {};
    return promiseA(paramsA).then(function(resultA){
        results.a = resultA;
        return promiseB(paramsB);
    }).then(function(resultB){
        results.b = resultB;
        return promiseC(paramsC);
    }).then(function(resultC){
        //Resolve with composite of all promises
        return Promise.resolve(results.a + results.b + resultC);
    }).catch(function(error){
        return Promise.reject(error);
    });
}
  • 全局变量是错误的,因此此解决方案使用局部范围的变量,不会造成损害。它只能在函数中访问。
  • 可变状态是丑陋的,但这不会以丑陋的方式改变状态。丑陋的可变状态传统上是指修改函数自变量或全局变量的状态,但是此方法仅修改了局部范围的变量的状态,该变量的存在仅出于汇总promise结果的目的……一个将死于简单死亡的变量一旦诺言解决。
  • 不会阻止中间承诺访问结果对象的状态,但这不会引入某些可怕的情况,在这种情况下,链中的一个承诺会流氓并破坏您的结果。在promise的每个步骤中设置值的责任仅限于此功能,并且总体结果将是正确的还是不正确的...它将不会是会在生产后多年出现的一些错误(除非您打算这样做) !)
  • 这不会引入由并行调用引起的竞争条件场景,因为将为getExample函数的每次调用创建一个结果变量的新实例。
泡芙村村Pro2020/03/11 20:27:14

ECMAScript和声

当然,语言设计者也意识到了这个问题。他们做了很多工作,异步函数提案最终使它成为了

ECMAScript 8

您不再需要单个then调用或回调函数,因为在异步函数(被调用时返回一个Promise)中,您只需等待Promise直接解析即可。它还具有诸如条件,循环和try-catch-clauses之类的任意控制结构,但是为了方便起见,我们在这里不需要它们:

async function getExample() {
    var resultA = await promiseA(…);
    // some processing
    var resultB = await promiseB(…);
    // more processing
    return // something using both resultA and resultB
}

ECMAScript 6

在等待ES8时,我们已经使用了非常相似的语法。ES6带有生成器功能,该功能允许按任意放置的yield关键字将执行分段这些切片可以相互独立,甚至异步地运行-这就是我们要在执行下一步之前等待promise解析时所要做的。

有专用的库(例如cotask.js),但是还有许多Promise库具有辅助函数(QBluebirdwhen …),当您为它们提供生成器函数时,它们会为您逐步异步执行产生希望。

var getExample = Promise.coroutine(function* () {
//               ^^^^^^^^^^^^^^^^^ Bluebird syntax
    var resultA = yield promiseA(…);
    // some processing
    var resultB = yield promiseB(…);
    // more processing
    return // something using both resultA and resultB
});

This did work in Node.js since version 4.0, also a few browsers (or their dev editions) did support generator syntax relatively early.

ECMAScript 5

However, if you want/need to be backwards-compatible you cannot use those without a transpiler. Both generator functions and async functions are supported by the current tooling, see for example the documentation of Babel on generators and async functions.

And then, there are also many other compile-to-JS languages that are dedicated to easing asynchronous programming. They usually use a syntax similar to await, (e.g. Iced CoffeeScript), but there are also others that feature a Haskell-like do-notation (e.g. LatteJs, monadic, PureScript or LispyScript).

老丝猿阿飞2020/03/11 20:27:14

同步检查

为变量分配可满足的承诺值,然后通过同步检查获取其值。该示例使用bluebird的.value()方法,但是许多库提供了类似的方法。

function getExample() {
    var a = promiseA(…);

    return a.then(function() {
        // some processing
        return promiseB(…);
    }).then(function(resultB) {
        // a is guaranteed to be fulfilled here so we can just retrieve its
        // value synchronously
        var aValue = a.value();
    });
}

您可以将其用于任意多个值:

function getExample() {
    var a = promiseA(…);

    var b = a.then(function() {
        return promiseB(…)
    });

    var c = b.then(function() {
        return promiseC(…);
    });

    var d = c.then(function() {
        return promiseD(…);
    });

    return d.then(function() {
        return a.value() + b.value() + c.value() + d.value();
    });
}
泡芙村村Pro2020/03/11 20:27:14

显式传递

与嵌套回调类似,此技术依赖于闭包。但是,链条保持不变-不仅传递最新结果,而且每个步骤都传递一些状态对象。这些状态对象会累积先前操作的结果,并传回以后将再次需要的所有值以及当前任务的结果。

function getExample() {
    return promiseA(…).then(function(resultA) {
        // some processing
        return promiseB(…).then(b => [resultA, b]); // function(b) { return [resultA, b] }
    }).then(function([resultA, resultB]) {
        // more processing
        return // something using both resultA and resultB
    });
}

在这里,那个小箭头b => [resultA, b]是关闭函数,resultA并将两个结果的数组传递给下一步的函数它使用参数解构语法将其再次分解为单个变量。

在ES6进行解构之前.spread(),许多Promise库(QBluebirdwhen,…)提供了一种称为nifty的辅助方法它需要一个具有多个参数的函数-每个数组元素一个-用作.spread(function(resultA, resultB) { …

当然,这里所需的关闭可以通过一些辅助功能进一步简化,例如

function addTo(x) {
    // imagine complex `arguments` fiddling or anything that helps usability
    // but you get the idea with this simple one:
    return res => [x, res];
}

return promiseB(…).then(addTo(resultA));

另外,您可以雇用Promise.all为数组产生promise:

function getExample() {
    return promiseA(…).then(function(resultA) {
        // some processing
        return Promise.all([resultA, promiseB(…)]); // resultA will implicitly be wrapped
                                                    // as if passed to Promise.resolve()
    }).then(function([resultA, resultB]) {
        // more processing
        return // something using both resultA and resultB
    });
}

而且,您不仅可以使用数组,还可以使用任意复杂的对象。例如,使用_.extendObject.assign在其他辅助函数中:

function augment(obj, name) {
    return function (res) { var r = Object.assign({}, obj); r[name] = res; return r; };
}

function getExample() {
    return promiseA(…).then(function(resultA) {
        // some processing
        return promiseB(…).then(augment({resultA}, "resultB"));
    }).then(function(obj) {
        // more processing
        return // something using both obj.resultA and obj.resultB
    });
}

While this pattern guarantees a flat chain and explicit state objects can improve clarity, it will become tedious for a long chain. Especially when you need the state only sporadically, you still have to pass it through every step. With this fixed interface, the single callbacks in the chain are rather tightly coupled and inflexible to change. It makes factoring out single steps harder, and callbacks cannot be supplied directly from other modules - they always need to be wrapped in boilerplate code that cares about the state. Abstract helper functions like the above can ease the pain a bit, but it will always be present.