首页 文章

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

提问于
浏览
538

我已将我的代码重组为promises,并构建了一个很棒的长 flat promise chain ,由多个 .then() 回调组成 . 最后我想返回一些复合值,并且需要访问多个 intermediate promise results . 但是,序列中间的分辨率值不在最后一个回调的范围内,我该如何访问它们?

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

15 回答

  • 51

    我想你可以使用RSVP的哈希值 .

    如下所示:

    const mainPromise = () => {
            const promise1 = new Promise((resolve, reject) => {
                setTimeout(() => {
                    console.log('first promise is completed');
                    resolve({data: '123'});
                }, 2000);
            });
    
            const promise2 = new Promise((resolve, reject) => {
                setTimeout(() => {
                    console.log('second promise is completed');
                    resolve({data: '456'});
                }, 2000);
            });
    
            return new RSVP.hash({
                  prom1: promise1,
                  prom2: promise2
              });
    
        };
    
    
       mainPromise()
        .then(data => {
            console.log(data.prom1);
            console.log(data.prom2);
        });
    
  • 30

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

    试试这个:

    async function getExample(){
    
      let response = await returnPromise();
    
      let response2 = await returnPromise2();
    
      console.log(response, response2)
    
    }
    
    getExample()
    

    并运行该文件:

    node --harmony-async-await getExample.js

    简单就可以!

  • 5

    同步检查

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

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

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

  • 200

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

    根据how-to-chain-javascript-promises

    好吧,让我们来看看代码:

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

    嵌套(和)闭包

    使用闭包来维护变量的范围(在我们的例子中,成功回调函数参数)是自然的JavaScript解决方案 . 有了promises,我们可以任意回调 - 它们在语义上是等价的,除了内部的范围 .

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

    当然,这是 Build 一个缩进金字塔 . 如果缩进变得太大,您仍然可以应用旧工具来对抗pyramid of doom:modularize,使用额外的命名函数,并在不再需要变量时立即展平promise链 .
    理论上,你总是可以避免两个以上的嵌套级别(通过使所有闭包显式化),在实践中使用尽可能多的合理 .

    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 application的辅助函数,如Underscore / lodashnative .bind() method_.partial ,以进一步减少缩进:

    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
    }
    
  • 2

    可变的上下文状态

    琐碎(但不优雅且相当错误)的解决方案是只使用更高范围的变量(链中的所有回调都可以访问)并在获取它们时将结果值写入它们:

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

    可以使用(最初为空的)对象而不是许多变量,在该对象上将结果存储为动态创建的属性 .

    该解决方案有几个缺点:

    • Mutable state is uglyglobal variables are evil .

    • 这种模式不能跨函数边界工作,模块化函数更难,因为它们的声明不能离开共享范围

    • 变量的范围不会阻止在初始化之前访问它们 . 这尤其适用于可能发生竞争条件的复杂承诺构造(循环,分支,排除) . 明确地传递状态,承诺鼓励,强制更清晰的编码风格,可以防止这种情况 .

    • 必须正确选择这些共享变量的范围 . 它必须是执行函数的本地函数,以防止多个并行调用之间的竞争条件,例如,如果状态存储在实例上就是这种情况 .

    Bluebird库鼓励使用传递的对象,使用their bind() method将上下文对象分配给promise链 . 它可以通过其他不可用的this keyword从每个回调函数访问 . 虽然对象属性比变量更容易发现未检测到的拼写错误,但模式非常巧妙:

    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
    }
    

    这种方法可以很容易地在不支持.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));
    }
    
  • 1

    明确的传递

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

    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进行解构之前,许多promise库(QBluebirdwhen,...)提供了一个名为 .spread() 的漂亮助手方法 . 它需要一个具有多个参数的函数 - 每个数组元素一个 - 用作 .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 来生成数组的承诺:

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

    虽然这种模式保证了扁平链条,而显式状态对象可以提高清晰度,但对于长链来说,这将变得乏味 . 特别是当您偶尔需要状态时,您仍然必须通过每一步 . 通过这个固定的接口,链中的单个回调相互紧密耦合,并且不灵活 . 它使得单个步骤的分解变得更加困难,并且不能直接从其他模块提供回调 - 它们总是需要包含在关心状态的样板代码中 . 像上面这样的抽象辅助函数可以缓解疼痛,但它总会存在 .

  • 1

    打破链条

    当您需要访问链中的中间值时,您应该将链条拆分成您需要的那些单件 . 而不是附加一个回调并以某种方式尝试多次使用其参数,将多个回调附加到同一个承诺 - 无论您需要结果值 . 别忘了,promise just represents (proxies) a future value!接下来,在线性链中从另一个派生一个承诺,使用库提供给您的promise组合器来构建结果值 .

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

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

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

    Bluebird还具有专用的join function,用更简单(更高效)的构造替换 Promise.all spread 组合:

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

    另一个答案,使用顺序 Actuator nsynjs

    function getExample(){
    
      var response1 = returnPromise1().data;
    
      // promise1 is resolved at this point, '.data' has the result from resolve(result)
    
      var response2 = returnPromise2().data;
    
      // promise2 is resolved at this point, '.data' has the result from resolve(result)
    
      console.log(response, response2);
    
    }
    
    nynjs.run(getExample,{},function(){
        console.log('all done');
    })
    

    更新:添加了工作示例

    function synchronousCode() {
         var urls=[
             "https://ajax.googleapis.com/ajax/libs/jquery/1.7.0/jquery.min.js",
             "https://ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js",
             "https://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"
         ];
         for(var i=0; i<urls.length; i++) {
             var len=window.fetch(urls[i]).data.text().data.length;
             //             ^                   ^
             //             |                   +- 2-nd promise result
             //             |                      assigned to 'data'
             //             |
             //             +-- 1-st promise result assigned to 'data'
             //
             console.log('URL #'+i+' : '+urls[i]+", length: "+len);
         }
    }
    
    nsynjs.run(synchronousCode,{},function(){
        console.log('all done');
    })
    
    <script src="https://rawgit.com/amaksr/nsynjs/master/nsynjs.js"></script>
    
  • 5

    另一个答案,使用 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 瞧!

  • 1

    我不会在我自己的代码中使用这种模式,因为我不是使用全局变量的忠实粉丝 . 但是,在紧要关头它会起作用 .

    用户是一个有说服力的Mongoose模型 .

    var globalVar = '';
    
    User.findAsync({}).then(function(users){
      globalVar = users;
    }).then(function(){
      console.log(globalVar);
    });
    
  • 1

    ECMAScript Harmony

    当然,这个问题也得到了语言设计者的认可 . 他们做了很多工作,最终成功了

    ECMAScript 8

    您不再需要单个 then 调用或回调函数,如在异步函数中(在调用时返回promise),您只需等待promises直接解析即可 . 它还具有任意控制结构,如条件,循环和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附带了generator functions,允许在任意放置的 yield 关键字中将执行分开 . 这些切片可以相互独立地运行,甚至是异步运行 - 而这正是我们在运行下一步之前等待promise解析时所做的事情 .

    有专门的库(如cotask.js),但是当你给它们一个产生promises的生成器函数时,许多promise库都有辅助函数(QBluebirdwhen,...)为你做this async step-by-step execution .

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

    从版本4.0开始,这在Node.js中工作,也有一些浏览器(或他们的开发版)相对较早地支持生成器语法 .

    ECMAScript 5

    但是,如果您希望/需要向后兼容,则无法使用没有转换器的那些 . 当前工具支持生成器函数和异步函数,例如参见generatorsasync functions上的Babel文档 .

    然后,还有许多其他compile-to-JS languages致力于简化异步编程 . 它们通常使用类似于 await 的语法(例如Iced CoffeeScript),但也有一些具有类似Haskell的 do -notation(例如LatteJsmonadicPureScriptLispyScript) .

  • 6

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

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

    function getExample(){
        //locally scoped
        const results = {};
        return promiseA(...).then(function(resultA){
            results.a = resultA;
            return promiseB(...);
        }).then(function(resultB){
            results.b = resultB;
            return promiseC(...);
        }).then(function(resultC){
            //Resolve with composite of all promises
            return Promise.resolve(results.a + results.b + resultC);
        }).catch(function(error){
            return Promise.reject(error);
        });
    }
    
    • 全局变量很糟糕,因此该解决方案使用本地范围的变量,这不会造成任何伤害 . 它只能在功能中访问 .

    • 可变状态是丑陋的,但这不会以丑陋的方式改变状态 . 丑陋的可变状态传统上是指修改函数参数或全局变量的状态,但是这种方法只是修改了一个本地范围的变量的状态,该变量的唯一目的是聚合承诺结果......一旦承诺结算,将会导致简单死亡的变量 .

    • 中间承诺不会被阻止访问结果对象的状态,但这并没有引入一些可怕的场景,其中链中的一个承诺会变得流氓并破坏你的结果 . 在承诺的每个步骤中设置值的责任仅限于此函数,并且整体结果将是正确的或不正确的...它不会是多年后在 生产环境 中出现的错误(除非您打算将其用于!)

    • 这不会引入并行调用引起的竞争条件场景,因为为getExample函数的每次调用都会创建一个新的结果变量实例 .

  • 93

    使用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

相关问题