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

当前回答

同步检查

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

其他回答

嵌套(和)闭包

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

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

可变上下文状态

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

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 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 design that 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.

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
}

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

打破枷锁

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.

这将导致一个非常直接的控制流程,清晰的功能组合,因此易于模块化。

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之后的回调中进行参数解构。在ES5中,then调用将被许多承诺库(Q, Bluebird, when,…)提供的一个漂亮的助手方法所取代:.spread(function(resultA, resultB) {....

Bluebird还提供了一个专用的连接功能来取代承诺。All +扩展组合,更简单(更有效)的结构:

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