我将代码重构为承诺,并构建了一个奇妙的长而平坦的承诺链,由多个.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?
    });
}

当前回答

ECMAScript和谐

当然,语言设计者也认识到了这个问题。他们做了大量的工作,异步函数提案最终得以通过

ECMAScript 8

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

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关键字处将执行分开。这些切片可以彼此独立地、甚至异步地运行——当我们想要在运行下一步之前等待承诺解决时,我们就是这样做的。

有专门的库(如co或task.js),但也有许多承诺库有辅助函数(Q, Bluebird, when,…),当你给它们一个生成承诺的生成器函数时,它们会为你执行异步逐步执行。

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

这在Node.js 4.0版本起就可以了,也有一些浏览器(或他们的开发版本)相对较早地支持生成器语法。

ECMAScript 5

然而,如果你想要/需要向后兼容,你就不能在没有转译器的情况下使用它们。当前工具支持生成器函数和异步函数,例如,请参阅关于生成器和异步函数的Babel文档。

此外,还有许多其他的编译到js语言 它们致力于简化异步编程。它们通常使用类似于await的语法(例如Iced CoffeeScript),但也有其他一些具有类似haskell的do-notation(例如LatteJs, monadic, PureScript或LispyScript)。

其他回答

我不打算在我自己的代码中使用这种模式,因为我不太喜欢使用全局变量。然而,在紧要关头它还是有用的。

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

var globalVar = '';

User.findAsync({}).then(function(users){
  globalVar = users;
}).then(function(){
  console.log(globalVar);
});

另一个答案,使用顺序执行器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>

同步检查

将稍后需要的承诺值分配给变量,然后通过同步检查获得它们的值。这个例子使用了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();
    });
}

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

对于您提出的问题,使用局部作用域对象来收集承诺链中的中间结果是一种合理的方法。考虑下面的代码片段:

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.

示例在jsfiddle上可用

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

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