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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/28250680/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 01:28:45  来源:igfitidea点击:

How do I access previous promise results in a .then() chain?

javascriptscopepromisebluebirdes6-promise

提问by Bergi

I have restructured my code to promises, and built a wonderful long flat promise chain, consisting of multiple .then()callbacks. In the end I want to return some composite value, and need to access multiple intermediate promise results. However the resolution values from the middle of the sequence are not in scope in the last callback, how do I access them?

我已经将我的代码重构为promises,并构建了一个美妙的长扁平 promise 链,由多个.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?
    });
}

采纳答案by Bergi

ECMAScript Harmony

ECMAScript 和谐

Of course, this problem was recognized by the language designers as well. They did a lot of work and the async functions proposalfinally made it into

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

ECMAScript 8

ECMAScript 8

You don't need a single theninvocation or callback function any more, as in an asynchronous function (that returns a promise when being called) you can simply wait for promises to resolve directly. It also features arbitrary control structures like conditions, loops and try-catch-clauses, but for the sake of convenience we don't need them here:

您不再需要单个then调用或回调函数,因为在异步函数(在被调用时返回承诺)中,您可以简单地等待承诺直接解析。它还具有任意控制结构,如条件、循环和 try-catch-clause,但为了方便起见,我们在这里不需要它们:

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

ECMAScript 6

ECMAScript 6

While we were waiting for ES8, we already did use a very similar kind of syntax. ES6 came with generator functions, which allow to break the execution apart in pieces at arbitrarily placed yieldkeywords. Those slices can be run after each other, independently, even asynchronously - and that's just what we do when we want to wait for a promise resolution before running the next step.

在我们等待 ES8 的时候,我们已经使用了一种非常相似的语法。ES6 带有生成器函数,它允许在任意放置的yield关键字处将执行分开。这些切片可以相互独立,甚至异步运行 - 这就是我们想要在运行下一步之前等待承诺解决方案时所做的。

There are dedicated libraries (like coor task.js), but also many promise libraries have helper functions (Q, Bluebird, when, …) that do this async step-by-step executionfor you when you give them a generator function that yields promises.

有专用的库(如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.

自 4.0 版以来,这在 Node.js 中确实有效,而且一些浏览器(或其开发版本)确实较早地支持生成器语法。

ECMAScript 5

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 generatorsand async functions.

但是,如果您想要/需要向后兼容,则不能在没有转译器的情况下使用那些。当前工具支持生成器函数和异步函数,例如参见 Babel 关于生成器异步函数的文档。

And then, there are also many other compile-to-JS languagesthat 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, PureScriptor LispyScript).

然后,还有许多其他编译为 JS 的语言专门用于简化异步编程。他们通常使用类似语法await(例如冰的CoffeeScript),但也有其他人配备了哈斯克尔样do-notation(如LatteJs一元PureScriptLispyScript)。

回答by Bergi

Break the chain

打破链条

When you need to access the intermediate values in your chain, you should split your chain apart in those single pieces that you need. Instead of attaching one callback and somehow trying to use its parameter multiple times, attach multiple callbacks to the same promise - wherever you need the result value. Don't forget, a promise just represents (proxies) a future value! Next to deriving one promise from the other in a linear chain, use the promise combinators that are given to you by your library to build the result value.

当您需要访问链中的中间值时,您应该将链拆分为您需要的单个部分。与其附加一个回调并以某种方式尝试多次使用其参数,不如将多个回调附加到同一个 Promise - 无论您需要结果值的任何地方。不要忘记,promise 只是代表(代理)一个未来值!在从线性链中的另一个 promise 派生一个 promise 之后,使用您的库提供给您的 promise 组合子来构建结果值。

This will result in a very straightforward control flow, clear composition of functionalities and therefore easy modularisation.

这将导致非常简单的控制流程、清晰的功能组合,因此易于模块化。

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

Instead of the parameter destructuring in the callback after Promise.allthat only became avail­able with ES6, in ES5 the thencall would be replaced by a nifty helper method that was provided by many promise libraries (Q, Bluebird, when, …): .spread(function(resultA, resultB) { ….

之后回调中的参数解构Promise.all只在 ES6 中可用,在 ES5 中,then调用将被替换为许多承诺库(QBluebirdwhen,...)提供的漂亮帮助方法:.spread(function(resultA, resultB) { …

Bluebird also features a dedicated joinfunctionto replace that Promise.all+spreadcombination with a simpler (and more efficient) construct:

Bluebird 还具有专用join功能,可将Promise.all+spread组合替换为更简单(且更高效)的构造:

…
return Promise.join(a, b, function(resultA, resultB) { … });

回答by Esailija

Synchronous inspection

同步检测

Assigning promises-for-later-needed-values to variables and then getting their value via synchronous inspection. The example uses bluebird's .value()method but many libraries provide similar method.

将 promises-for-later-needed-values 分配给变量,然后通过同步检查获取它们的值。该示例使用 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();
    });
}

This can be used for as many values as you like:

这可以用于任意数量的值:

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();
    });
}

回答by Bergi

Nesting (and) closures

嵌套(和)闭包

Using closures for maintaining the scope of variables (in our case, the success callback function parameters) is the natural JavaScript solution. With promises, we can arbitrarily nest and flatten.then()callbacks - they are semantically equivalent, except for the scope of the inner one.

使用闭包来维护变量的范围(在我们的例子中,成功回调函数参数)是自然的 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;
        });
    });
}

Of course, this is building an indentation pyramid. If indentation is getting too large, you still can apply the old tools to counter the pyramid of doom: modularize, use extra named functions, and flatten the promise chain as soon as you don't need a variable any more.
In theory, you can always avoid more than two levels of nesting (by making all closures explicit), in practise use as many as are reasonable.

当然,这是在构建一个缩进金字塔。如果缩进太大,您仍然可以应用旧工具来对抗厄运金字塔:模块化,使用额外的命名函数,并在不再需要变量时立即展平承诺链。
理论上,您总是可以避免多于两层的嵌套(通过使所有闭包显式),在实践中尽可能多地使用。

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
    };
}

You can also use helper functions for this kind of partial application, like _.partialfrom Underscore/lodashor the native .bind()method, to further decrease indentation:

您还可以使用辅助功能对于这种局部的应用,如_.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
}

回答by Bergi

Explicit pass-through

显式传递

Similar to nesting the callbacks, this technique relies on closures. Yet, the chain stays flat - instead of passing only the latest result, some state object is passed for every step. These state objects accumulate the results of the previous actions, handing down all values that will be needed later again plus the result of the current task.

与嵌套回调类似,此技术依赖于闭包。然而,链保持平坦——不是只传递最新的结果,而是每一步都传递一些状态对象。这些状态对象累积先前操作的结果,传递以后再次需要的所有值以及当前任务的结果。

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
    });
}

Here, that little arrow b => [resultA, b]is the function that closes over resultA, and passes an array of both results to the next step. Which uses parameter destructuring syntax to break it up in single variables again.

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

Before destructuring became available with ES6, a nifty helper method called .spread()was pro­vi­ded by many promise libraries (Q, Bluebird, when, …). It takes a function with multiple parameters - one for each array element - to be used as .spread(function(resultA, resultB) { ….

在 ES6 可以使用解构之前.spread(),许多 Promise 库(QBluebirdwhen……)都提供了一个漂亮的辅助方法调用。它需要一个带有多个参数的函数 - 每个数组元素一个 - 用作.spread(function(resultA, resultB) { ….

Of course, that closure needed here can be further simplified by some helper functions, e.g.

当然,这里需要的闭包可以通过一些辅助函数进一步简化,例如

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));

Alternatively, you can employ Promise.allto produce the promise for the array:

或者,您可以使用Promise.all生成数组的承诺:

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
    });
}

And you might not only use arrays, but arbitrarily complex objects. For example, with _.extendor Object.assignin a different helper function:

您不仅可以使用数组,还可以使用任意复杂的对象。例如,使用_.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.

虽然这种模式保证了一个扁平的链并且明确的状态对象可以提高清晰度,但对于长链来说它会变得乏味。特别是当您只是偶尔需要状态时,您仍然需要通过每一步。有了这个固定的接口,链中的单个回调就非常紧密地耦合在一起,并且难以改变。它使分解单个步骤变得更加困难,并且不能直接从其他模块提供回调——它们总是需要包装在关心状态的样板代码中。像上面这样的抽象辅助函数可以稍微缓解一些痛苦,但它会一直存在。

回答by Bergi

Mutable contextual state

可变上下文状态

The trivial (but inelegant and rather errorprone) solution is to just use higher-scope variables (to which all callbacks in the chain have access) and write result values to them when you get them:

微不足道(但不优雅且相当容易出错)的解决方案是仅使用更高范围的变量(链中的所有回调都可以访问)并在获得结果值时将结果值写入它们:

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

Instead of many variables one might also use an (initially empty) object, on which the results are stored as dynamically created properties.

除了许多变量之外,还可以使用(最初为空的)对象,在该对象上将结果存储为动态创建的属性。

This solution has several drawbacks:

该解决方案有几个缺点:

  • Mutable state is ugly, and global variables are evil.
  • This pattern doesn't work across function boundaries, modularising the functions is harder as their declarations must not leave the shared scope
  • The scope of the variables does not prevent to access them before they are initialized. This is especially likely for complex promise constructions (loops, branching, excptions) where race conditions might happen. Passing state explicitly, a declarative designthat promises encourage, forces a cleaner coding style which can prevent this.
  • One must choose the scope for those shared variables correctly. It needs to be local to the executed function to prevent race conditions between multiple parallel invocations, as would be the case if, for example, state was stored on an instance.
  • 可变状态是丑陋的全局变量是邪恶的
  • 这种模式不能跨函数边界工作,模块化函数更难,因为它们的声明不能离开共享范围
  • 变量的范围不会阻止在初始化之前访问它们。这对于可能发生竞争条件的复杂承诺结构(循环、分支、异常)尤其有可能。显式传递状态,一种承诺鼓励的声明式设计,强制采用更简洁的编码风格,可以防止这种情况发生。
  • 必须正确选择这些共享变量的范围。它需要位于执行的函数的本地,以防止多个并行调用之间的竞争条件,例如,如果状态存储在实例上,就会出现这种情况。

The Bluebird library encourages the use of an object that is passed along, using their bind()methodto assign a context object to a promise chain. It will be accessible from each callback function via the otherwise unusable thiskeyword. While object properties are more prone to undetected typos than variables, the pattern is quite clever:

Bluebird 库鼓励使用传递的对象,使用他们的bind()方法将上下文对象分配给承诺链。可以通过其他不可用的this关键字从每个回调函数访问它。虽然对象属性比变量更容易出现未检测到的拼写错误,但该模式非常聪明:

function getExample() {
    return promiseA(…)
    .bind({}) // Bluebird only!
    .then(function(resultA) {
        this.resultA = resultA;
        // some processing
        return promiseB(…);
    }).then(function(resultB) {
        // more processing
        return // something using both this.resultA and resultB
    }).bind(); // don't forget to unbind the object if you don't want the
               // caller to access it
}

This approach can be easily simulated in promise libraries that do not support .bind (although in a somewhat more verbose way and cannot be used in an expression):

这种方法可以在不支持 .bind 的 Promise 库中轻松模拟(尽管以更冗长的方式并且不能在表达式中使用):

function getExample() {
    var ctx = {};
    return promiseA(…)
    .then(function(resultA) {
        this.resultA = resultA;
        // some processing
        return promiseB(…);
    }.bind(ctx)).then(function(resultB) {
        // more processing
        return // something using both this.resultA and resultB
    }.bind(ctx));
}

回答by Jay

A less harsh spin on "Mutable contextual state"

不那么苛刻的“可变上下文状态”

Using a locally scoped object to collect the intermediate results in a promise chain is a reasonable approach to the question you posed. Consider the following snippet:

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

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);
    });
}
  • Global variables are bad, so this solution uses a locally scoped variable which causes no harm. It is only accessible within the function.
  • Mutable state is ugly, but this does not mutate state in an ugly manner. The ugly mutable state traditionally refers to modifying the state of function arguments or global variables, but this approach simply modifies the state of a locally scoped variable that exists for the sole purpose of aggregating promise results...a variable that will die a simple death once the promise resolves.
  • Intermediate promises are not prevented from accessing the state of the results object, but this does not introduce some scary scenario where one of the promises in the chain will go rogue and sabotage your results. The responsibility of setting the values in each step of the promise is confined to this function and the overall result will either be correct or incorrect...it will not be some bug that will crop up years later in production (unless you intend it to!)
  • This does not introduce a race condition scenario that would arise from parallel invocation because a new instance of the results variable is created for every invocation of the getExample function.
  • 全局变量很糟糕,所以这个解决方案使用了一个不会造成伤害的局部范围变量。它只能在函数内访问。
  • 可变状态是丑陋的,但这不会以丑陋的方式改变状态。丑陋的可变状态传统上是指修改函数参数或全局变量的状态,但这种方法只是修改局部范围变量的状态,该变量存在的唯一目的是聚合承诺结果......一个将简单死亡的变量一旦承诺解决。
  • 不会阻止中间承诺访问结果对象的状态,但这不会引入一些可怕的场景,其中链中的一个承诺会流氓并破坏您的结果。在 promise 的每一步中设置值的责任仅限于这个函数,整体结果要么正确要么不正确......它不会是几年后在生产中突然出现的错误(除非你打算这样做) !)
  • 这不会引入由并行调用引起的竞争条件场景,因为每次调用 getExample 函数都会创建一个新的结果变量实例。

回答by Anthony

Node 7.4 now supports async/await calls with the harmony flag.

Node 7.4 现在支持带有和声标志的 async/await 调用。

Try this:

尝试这个:

async function getExample(){

  let response = await returnPromise();

  let response2 = await returnPromise2();

  console.log(response, response2)

}

getExample()

and run the file with:

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

node --harmony-async-await getExample.js

node --harmony-async-await getExample.js

Simple as can be!

尽可能简单!

回答by yzfdjzwl

This days, I also hava meet some questions like you. At last, I find a good solution with the quesition, it's simple and good to read. I hope this can help you.

这几天,我也遇到了一些像你一样的问题。最后,我找到了一个很好的解决方案,简单易读。我希望这可以帮助你。

According to how-to-chain-javascript-promises

根据how-to-chain-javascript-promises

ok, let's look at the code:

好的,让我们看一下代码:

const firstPromise = () => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('first promise is completed');
            resolve({data: '123'});
        }, 2000);
    });
};

const secondPromise = (someStuff) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('second promise is completed');
            resolve({newData: `${someStuff.data} some more data`});
        }, 2000);
    });
};

const thirdPromise = (someStuff) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('third promise is completed');
            resolve({result: someStuff});
        }, 2000);
    });
};

firstPromise()
    .then(secondPromise)
    .then(thirdPromise)
    .then(data => {
        console.log(data);
    });

回答by Anthony

Another answer, using babel-nodeversion <6

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

Using async - await

使用 async - await

npm install -g [email protected]

npm install -g [email protected]

example.js:

example.js:

async function getExample(){

  let response = await returnPromise();

  let response2 = await returnPromise2();

  console.log(response, response2)

}

getExample()

Then, run babel-node example.jsand voila!

然后,跑babel-node example.js,瞧!