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

当前回答

嵌套(和)闭包

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

其他回答

嵌套(和)闭包

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

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

同步检查

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

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

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

var globalVar = '';

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

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

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