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

Javascript 如何访问.then()链中以前的承诺结果?,javascript,scope,promise,bluebird,es6-promise,Javascript,Scope,Promise,Bluebird,Es6 Promise,我将我的代码重组为,并构建了一个奇妙的长平面承诺链,由多个.then()回调组成。最后,我想返回一些复合值,并需要访问多个中间承诺结果。但是,序列中间的分辨率值不在上一次回调的范围内,如何访问它们 function getExample() { return promiseA(…).then(function(resultA) { // Some processing return promiseB(…); }).then(function(res

我将我的代码重组为,并构建了一个奇妙的长平面承诺链,由多个
.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?
    });
}
嵌套(和)闭包 使用闭包来维护变量的作用域(在我们的例子中是success回调函数参数)是自然的JavaScript解决方案。有了承诺,我们可以任意
.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
    };
}
您还可以对此类缩进使用辅助函数,如从/或的
\uu.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
}
显式传递 与嵌套回调类似,这种技术依赖于闭包。然而,链保持平坦-不是只传递最新的结果,而是为每个步骤传递一些状态对象。这些状态对象累积以前操作的结果,将以后需要的所有值再加上当前任务的结果进行传递

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库(、、…)支持一种称为
.spread()
的漂亮助手方法。它使用一个具有多个参数的函数(每个数组元素一个参数)作为
.spread(函数(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
为数组生成Promise:

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
    });
}
您可能不仅使用数组,还可以使用任意复杂的对象。例如,使用或在不同的辅助函数中:

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
    });
}
虽然此模式保证了平面链,并且显式状态对象可以提高清晰度,但对于长链来说,它将变得单调乏味。特别是当您偶尔需要状态时,您仍然必须将其贯穿每一步。有了此固定接口,链中的单个回调非常紧密地耦合在一起,并且无法灵活地进行更改。这使得分解单个步骤变得更加困难,并且不能直接从其他模块提供回调-它们总是需要包装在关心状态的样板代码中。像上面这样的抽象帮助函数可以减轻一些痛苦,但它总是存在的。

ECMAScript Harmony 当然,语言设计者也认识到了这个问题。他们做了大量的工作,最终将其转化为

ECMAScript 8 您不再需要像在异步函数(在被调用时返回承诺)中那样的单个
then
调用或回调函数您只需等待承诺直接解析即可。它还具有任意控制结构,如条件、循环和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
关键字将执行拆分为多个部分。这些部分可以相互独立地运行,甚至异步地运行——这正是我们在等待承诺解决时所做的在运行下一步之前,请执行以下操作

有专门的库(如或),但也有许多promise库具有帮助函数(、…),当您为它们提供生成承诺的生成器函数时,它们可以为您提供帮助

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 但是,如果您希望/需要向后兼容,则不能在没有transpiler的情况下使用这些函数。当前工具支持生成器函数和异步函数,请参阅Babel on和的文档

然后,还有很多其他的 它们通常使用类似于
await
(例如)的语法,但也有其他一些语法具有类似于
do
-符号(例如,or)的Haskell特性 简单(但不雅观且容易出错)的解决方案是只使用范围更大的变量(链中的所有回调都可以访问该变量)并在获得结果值时将结果值写入它们:

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
    });
}
也可以使用(最初为空)对象代替许多变量,在该对象上,结果作为动态创建的属性存储

此解决方案有几个缺点:

  • ,及
  • 这种模式不能跨函数边界工作,因为函数的声明不能离开共享作用域,所以函数更难实现模块化
  • 变量的作用域不会阻止在初始化变量之前访问它们。这尤其适用于可能发生争用条件的复杂承诺构造(循环、分支、例外)。明确传递状态(承诺鼓励)会强制使用更干净的编码样式来防止这种情况
  • 必须为这些共享变量选择范围
    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
    }
    
    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 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
        });
    }
    
    …
    return Promise.join(a, b, function(resultA, resultB) { … });
    
    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);
    });
    
    async function getExample(){
    
      let response = await returnPromise();
    
      let response2 = await returnPromise2();
    
      console.log(response, response2)
    
    }
    
    getExample()
    
    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;
    });
    
    async function getExample(){
    
      let response = await returnPromise();
    
      let response2 = await returnPromise2();
    
      console.log(response, response2)
    
    }
    
    getExample()
    
    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?
        });
    }
    
    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);
        });
    }
    
    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');
    })
    
    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);
        });
    
        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);
        });
    
    // Get info asynchronously from a server
    function pGetServerInfo()
        {
        // then value: "server info"
        } // pGetServerInfo
    
    // Write into a file asynchronously
    function pWriteFile(path,string)
        {
        // no then value
        } // pWriteFile
    
    // The heart of the solution: Write formatted info into a log file asynchronously,
    // using the pGetServerInfo and pWriteFile operations
    function pLogInfo(localInfo)
        {
        var scope={localInfo:localInfo}; // Create an explicit scope object
        var thenFunc=p2.bind(scope); // Create a temporary function with this scope
        return (pGetServerInfo().then(thenFunc)); // Do the next 'then' in the chain
        } // pLogInfo
    
    // Scope of this 'then' function is {localInfo:localInfo}
    function p2(serverInfo)
        {
        // Do the final 'then' in the chain: Writes "local info, server info"
        return pWriteFile('log',this.localInfo+','+serverInfo);
        } // p2
    
    pLogInfo("local info").then().catch(err);
    
    /**
     * Promise like object that allows to resolve it promise from outside code. Example:
     *
    ```
    class Api {
      fooReady = new Deferred<Data>()
      private knower() {
        inOtherMoment(data=>{
          this.fooReady.resolve(data)
        })
      }
    }
    ```
     */
    var Deferred = /** @class */ (function () {
      function Deferred(callback) {
        var instance = this;
        this.resolve = null;
        this.reject = null;
        this.status = 'pending';
        this.promise = new Promise(function (resolve, reject) {
          instance.resolve = function () { this.status = 'resolved'; resolve.apply(this, arguments); };
          instance.reject = function () { this.status = 'rejected'; reject.apply(this, arguments); };
        });
        if (typeof callback === 'function') {
          callback.call(this, this.resolve, this.reject);
        }
      }
      Deferred.prototype.then = function (resolve) {
        return this.promise.then(resolve);
      };
      Deferred.prototype.catch = function (r) {
        return this.promise.catch(r);
      };
      return Deferred;
    }());