你如何正确地从承诺中返回多个值?

时间:2015-02-24 18:36:43

标签: javascript promise q

我最近遇到过几次某种情况,我不知道如何正确解决。假设以下代码:

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

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

现在可能会出现我想要amazingDataafterSomethingElse访问权限的情况。

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

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

9 个答案:

答案 0 :(得分:67)

您无法解决具有多个属性的承诺,就像您无法从函数返回多个值一样。承诺在概念上代表一段时间内的价值,因此当您可以表示复合价值时,您无法在承诺中加入多个值。

承诺本身可以解决单一价值 - 这是Q如何运作,how the Promises/A+ spec works以及the abstraction如何运作的一部分。

您最接近的是使用Q.spread并返回数组或使用ES6解构(如果它支持或您愿意使用像BabelJS这样的转换工具。

关于在承诺链中传递上下文,请参阅Bergi's excellent canonical on that

答案 1 :(得分:19)

你只能传递一个值,但它可以是一个具有多个值的数组,例如:

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

同时致电承诺,将它们链接起来:

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

答案 2 :(得分:18)

您可以返回包含两个值的对象 - 这没有任何问题。

另一个策略是通过闭包保持值,而不是通过它:

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

答案 3 :(得分:5)

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

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
}

答案 4 :(得分:2)

以下是我认为你应该做的事情。

拆分链

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

由于您的示例正在运行某些代码,我认为它都是在函数内声明的。我将其称为 toto()。 然后我们将有另一个函数运行 afterSomething() afterSomethingElse()

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

您还会注意到我添加了一个 return 语句,因为它通常是Promises的方式 - 您总是返回一个承诺,以便我们可以在需要时继续链接。在这里, 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()正在执行异步操作,则无关紧要。如果是这样,将返回承诺并且链可以继续。如果不是,则返回结果值。但是因为函数是从 then()调用的,所以无论如何都会将值包装到一个promise中(至少在原始Javascript中)。

processAsync asynchronous

如果 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()。它可以是异步的,也可以不是。将返回承诺,或将值包含在已解决的承诺中。

你的编码风格与我用来做的非常接近,这就是我在2年后回答的原因。 我不喜欢到处都有匿名功能。我觉得很难读。即使它在社区中很常见。 就像我们用 promise-purgatory 替换 callback-hell 一样。

我还希望将中的函数名称保留为。它们只会在本地定义。 大多数时候,他们会调用其他地方定义的另一个函数 - 这样可重用 - 来完成这项工作。 我甚至为只有1个参数的函数执行此操作,因此当我向函数签名添加/删除参数时,我不需要进入和退出函数。

吃饭示例

以下是一个例子:

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()。这只是创建已解决承诺的快捷方式。 我试图实现的目标是让我在一个位置运行所有代码 - 就在 thens 下面。 所有其他具有更具描述性名称的函数都是可重用的。

这种技术的缺点是它定义了很多功能。 但是我担心这是一种必要的痛苦,以避免在整个地方都有匿名功能。 无论如何风险是什么:堆栈溢出? (笑话!)

使用其他答案中定义的数组或对象也可以。在某种程度上,这是answer proposed by Kevin Reid

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

使用bind

如果你想保持你的功能可重用但不需要保持内部的内容非常短,你可以使用 bind()

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

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

为了简单起见, bind()会在调用函数时将args列表(第一个除外)添加到函数中。

使用Promise.all

在你的帖子中,你提到了使用 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() - 注意数组的存在 - 只要是promises,但要确保没有任何promise会失败,否则会停止处理。

而不是从 args 参数定义新变量,您应该能够使用 spread()而不是 then()各种令人敬畏的工作。

答案 5 :(得分:2)

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

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

答案 6 :(得分:1)

无论你从承诺中返回什么,都将被包含在下一个.then()阶段展开的承诺中。

当您需要将一个或多个承诺与一个或多个同步值一起返回时会变得很有趣,例如;

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 */);

答案 7 :(得分:0)

您可以选中由Rxjs表示的可观察,让您返回个以上值。

答案 8 :(得分:0)

只需返回一个元组:

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

}