你如何正确地 return 来自 Promise 的多个值?

How do you properly return multiple values from a Promise?

我最近运行遇到了几次特定的情况,我不知道如何正确解决。假设以下代码:

somethingAsync()
  .then( afterSomething )
  .then( afterSomethingElse )
  
function afterSomething( amazingData ) {
  return processAsync( amazingData );
}
function afterSomethingElse( processedData ) {
}

现在可能会出现我想访问 afterSomethingElse 中的 amazingData 的情况。

一个明显的解决方案是 return 来自 afterSomething 的数组或散列,因为,好吧,你只能 return 来自函数的一个值。但我想知道是否有办法让 afterSomethingElse 接受 2 个参数并同样调用它,因为这似乎更容易记录和理解。

我只是想知道这种可能性,因为有 Q.spread,它做的事情与我想要的类似。

您无法解决具有多个属性的承诺,就像您无法return 函数中的多个值一样。 promise 在概念上表示随时间变化的值,因此虽然您可以表示复合值,但不能在 promise 中放置多个值。

一个 promise 本质上用一个单一的值来解决 - 这是 Q 工作方式的一部分,how the Promises/A+ spec works and how the abstraction 工作。

最接近的是使用 Q.spread 和 return 数组或使用 ES6 解构(如果它受支持)或者您愿意使用像 BabelJS 这样的转译工具。

至于将上下文传递到 promise 链中,请参阅

您可以 return 包含两个值的对象 — 这没有错。

另一种策略是通过闭包保留值,而不是通过它传递:

somethingAsync().then(afterSomething);

function afterSomething(amazingData) {
  return processAsync(amazingData).then(function (processedData) {
    // both amazingData and processedData are in scope here
  });
}

完全而不是部分内联形式(等效,可以说更一致):

somethingAsync().then(function (amazingData) {
  return processAsync(amazingData).then(function (processedData) {
    // both amazingData and processedData are in scope here
  });
}

只能传递一个值,但可以是一个数组,其中包含多个值,例如:

function step1(){
  let server = "myserver.com";
  let data = "so much data, very impresive";
  return Promise.resolve([server, data]);
}

另一方面,您可以使用 ES2015 的 解构 表达式来获取各个值。

function step2([server, data]){
  console.log(server); // print "myserver.com"
  console.log(data);   // print "so much data, very impresive"
  return Promise.resolve("done");
}

调用这两个 promise,将它们链接起来:

step1()
.then(step2)
.then((msg)=>{
  console.log(msg); // print "done"
})

你可以做两件事,return一个对象

somethingAsync()
    .then( afterSomething )
    .then( afterSomethingElse );

function processAsync (amazingData) {
     //processSomething
     return {
         amazingData: amazingData, 
         processedData: processedData
     };
}

function afterSomething( amazingData ) {
    return processAsync( amazingData );
}

function afterSomethingElse( dataObj ) {
    let amazingData = dataObj.amazingData,
        processedData = dataObj.proccessedData;
}

使用范围!

var amazingData;
somethingAsync()
  .then( afterSomething )
  .then( afterSomethingElse )

function afterSomething( returnedAmazingData ) {
  amazingData = returnedAmazingData;
  return processAsync( amazingData );
}
function afterSomethingElse( processedData ) {
  //use amazingData here
}

这是我认为你应该做的。

拆分链

因为这两个函数都将使用 amazingData,所以将它们放在一个专用函数中是有意义的。 每当我想重用一些数据时,我通常都会这样做,所以它总是作为函数 arg 存在。

由于您的示例是 运行ning 一些代码,我假设它全部在函数内声明。我将其命名为 toto()。 然后我们将有另一个函数 运行 afterSomething()afterSomethingElse().

function toto() {
    return somethingAsync()
        .then( tata );
}

你也会注意到我添加了一个 return 声明,因为它通常是 Promises 的方式 - 你总是 return 一个承诺,所以我们如果需要,可以保持链接。在这里,somethingAsync() 将生成 amazingData 并且它将在新函数中的任何地方可用。

现在这个新函数的外观通常取决于processAsync() 是否也是异步的

processAsync 不是异步的

如果 processAsync() 不是异步的,没有理由把事情复杂化。一些旧的好的顺序代码就可以了。

function tata( amazingData ) {
    var processed = afterSomething( amazingData );
    return afterSomethingElse( amazingData, processed );
}

function afterSomething( amazingData ) {
    return processAsync( amazingData );
}
function afterSomethingElse( amazingData, processedData ) {
}

请注意,afterSomethingElse() 是否正在执行异步操作并不重要。如果是这样,一个承诺将被 returned 并且链可以继续。如果不是,则结果值将被 returned。但是因为该函数是从 then() 调用的,所以该值无论如何都会被包装到一个 promise 中(至少在原始 Javascript 中)。

processAsync 异步

如果 processAsync() 是异步的,代码看起来会略有不同。这里我们考虑 afterSomething()afterSomethingElse() 不会在其他任何地方重复使用。

function tata( amazingData ) {
    return afterSomething()
        .then( afterSomethingElse );

    function afterSomething( /* no args */ ) {
        return processAsync( amazingData );
    }
    function afterSomethingElse( processedData ) {
        /* amazingData can be accessed here */
    }
}

与之前 afterSomethingElse() 相同。它可以是异步的,也可以不是。承诺将被 returned,或者包装到已解决承诺中的值。


你的编码风格和我以前做的很接近,这就是为什么我在 2 年后回答的原因。 我不太喜欢到处使用匿名函数。我觉得很难读。即使它在社区中很常见。 就像我们用 promise-purgatory.

替换了 callback-hell

我还喜欢使 then 中的函数名称保持简短。无论如何,它们只会在本地定义。 大多数时候,他们会调用在别处定义的另一个函数——如此可重用——来完成这项工作。 我什至对只有 1 个参数的函数这样做,所以当我 add/remove 函数签名的参数时,我不需要输入和输出函数。

吃的例子

这是一个例子:

function goingThroughTheEatingProcess(plenty, of, args, to, match, real, life) {
    return iAmAsync()
        .then(chew)
        .then(swallow);

        function chew(result) {
            return carefullyChewThis(plenty, of, args, "water", "piece of tooth", result);
        }

        function swallow(wine) {
            return nowIsTimeToSwallow(match, real, life, wine);
        }
}

function iAmAsync() {
    return Promise.resolve("mooooore");
}

function carefullyChewThis(plenty, of, args, and, some, more) {
    return true;
}

function nowIsTimeToSwallow(match, real, life, bobool) {
}

不要过分关注 Promise.resolve()。这只是创建已解决承诺的一种快速方法。 我试图通过此实现的是将我 运行ning 中的所有代码放在一个位置 - 就在 thens 下方。 具有更具描述性的名称的所有其他函数都是可重用的。

这种技术的缺点是它定义了很多函数。 但为了避免到处都是匿名函数,恐怕这是一种必要的痛苦。 无论如何,风险是什么:堆栈溢出? (笑话!)


使用其他答案中定义的数组或对象也可以。这在某种程度上是 .

您还可以使用 bind()Promise.all()。 请注意,他们仍会要求您拆分代码。

使用绑定

如果你想让你的函数可重用,但又不需要让 then 中的内容非常短,你可以使用 bind().

function tata( amazingData ) {
    return afterSomething( amazingData )
        .then( afterSomethingElse.bind(null, amazingData) );
}

function afterSomething( amazingData ) {
    return processAsync( amazingData );
}
function afterSomethingElse( amazingData, processedData ) {
}

为简单起见,bind() 将在调用函数时将参数列表(第一个除外)添加到函数之前。

使用Promise.all

在您的 post 中,您提到了 spread() 的使用。我从未使用过您正在使用的框架,但这里是您应该能够使用它的方法。

有些人认为 Promise.all() 是所有问题的解决方案,所以我想它值得一提。

function tata( amazingData ) {
    return Promise.all( [ amazingData, afterSomething( amazingData ) ] )
        .then( afterSomethingElse );
}

function afterSomething( amazingData ) {
    return processAsync( amazingData );
}
function afterSomethingElse( args ) {
    var amazingData = args[0];
    var processedData = args[1];
}

您可以将数据传递给 Promise.all() - 注意数组的存在 - 只要承诺,但要确保 none承诺失败,否则它将停止处理。

而不是从 args 参数定义新变量,你应该能够使用 spread() 而不是 then() 用于各种出色的工作。

只需创建一个对象并从该对象中提取参数。

let checkIfNumbersAddToTen = function (a, b) {
return new Promise(function (resolve, reject) {
 let c = parseInt(a)+parseInt(b);
 let promiseResolution = {
     c:c,
     d : c+c,
     x : 'RandomString'
 };
 if(c===10){
     resolve(promiseResolution);
 }else {
     reject('Not 10');
 }
});
};

从 promiseResolution 中提取参数。

checkIfNumbersAddToTen(5,5).then(function (arguments) {
console.log('c:'+arguments.c);
console.log('d:'+arguments.d);
console.log('x:'+arguments.x);
},function (failure) {
console.log(failure);
});

无论你从承诺中 return 做什么,都将被包装到承诺中,以便在下一个 .then() 阶段解包。

当您需要 return 一个或多个 promise 以及一个或多个同步值时,这会变得很有趣,例如;

Promise.resolve([Promise.resolve(1), Promise.resolve(2), 3, 4])
       .then(([p1,p2,n1,n2]) => /* p1 and p2 are still promises */);

在这些情况下,必须使用 Promise.all() 来获得 p1p2 承诺在下一个 .then() 阶段展开,例如

Promise.resolve(Promise.all([Promise.resolve(1), Promise.resolve(2), 3, 4]))
       .then(([p1,p2,n1,n2]) => /* p1 is 1, p2 is 2, n1 is 3 and n2 is 4 */);

你可以勾选Observable代表Rxjs,让你return多个值。

简单地return一个元组:

    async add(dto: TDto): Promise<TDto> {
    console.log(`${this.storeName}.add(${dto})`);
    return firebase.firestore().collection(this.dtoName)
      .withConverter<TDto>(this.converter)
      .add(dto)
      .then(d => [d.update(this.id, d.id), d.id] as [any, string])
      .then(x => this.get(x[1]));
  }