您如何正确地从诺言中返回多个值?


86

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

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

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

现在可能会出现我想访问amazingDatain的情况afterSomethingElse

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

我只是想知道这种可能性,因为存在Q.spread,它的功能类似于我想要的。




在ES6中解构分配会有所帮助。检查这里
拉维特贾

Answers:


88

您无法解析具有多个属性的Promise,就像无法从一个函数返回多个值一样。一个诺言在概念上代表了一段时间内的价值,因此,当您可以表示复合值时,就不能在一个诺言中放置多个值。

一个诺言固有地以一个单一的值来解决-这是Q如何工作,Promises / A +规范如何工作以及抽象如何工作的一部分。

您能得到的最接近的是使用 Q.spread和return数组,或者在支持ES6的情况下使用ES6解构,或者您愿意使用BabelJS之类的转换工具。

至于在承诺链中传递上下文,请参考Bergi的出色规范


15
解决具有多个属性的对象有什么问题?似乎是从解析中获取多个值的简单方法。
jfriend00

5
这样做非常好
Benjamin Gruenbaum

您也可以将Promise扩展为.spread()蓝鸟,使其在此相关答案中显示:stackoverflow.com/a/22776850/1624862
Kevin Ghadyani

Promise.all()的行为似乎与此矛盾。 Promise.all([a, b, c]).then(function(x, y, z) {...})在x,y和z评估为a,b和c的解析值的所有现代Javascript引擎中都能正常工作。因此更准确地说,该语言不允许您从用户代码中轻松(或理智地)执行该操作(因为您可以直接从then子句返回Promise,因此可以将值包装在promise中,然后再用Promise包装这些值) .all()以获得所需的行为,尽管以复杂的方式)。
奥斯汀·赫默尔加恩

3
@AustinHemmelgarn那是错误的, Promise.all用array满足。在Promise.all([a,b]).then((a, b) => bundefined。这就是为什么你需要做的.then(([a, b]) =>是一个解构赋值
本杰明Gruenbaum

38

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

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

5
这称为解构,不是“解构器”,它不是运算符:-/
Bergi 2015年

3
顺便说一句,您可以在参数中使用解构:function step2([server, data]) { …-这样一来,您还可以避免分配给隐式全局变量。您确实应该在示例中使用returnor Promise.resolve,而不是new Promise构造函数。
Bergi

感谢@Bergi的推荐!
亚历杭德罗·席尔瓦

19

您可以返回一个同时包含两个值的对象,这没有错。

另一种策略是通过闭包而不是通过以下方式保留值:

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
可以返回then另一个内部then吗?这不是反模式吗?
robe007

就像@ robe007所说的,这类似于“回调地狱”吗?在这里,您的嵌套然后是块而不是回调函数,这将使实现诺言的目的无法实现
Dheeraj

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
}

3

我认为这是您应该做的。

分割链

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

当您的示例正在运行一些代码时,我将假设它们都在一个函数中声明。我将其称为toto()。然后,我们将有另一个函数将同时运行afterSomething()afterSomethingElse()

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

您还会注意到我添加了一个return语句,因为它通常是Promises的处理方式-您总是返回一个promise,因此我们可以根据需要保持链接。在这里,somethingAsync()将产生令人惊奇的数据,并且它将在新函数内的任何地方可用。

现在,这个新函数通常看起来像是依赖于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异步

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


您的编码风格与我以前的工作非常接近,这就是为什么即使两年后我仍然回答的原因。我不喜欢到处都有匿名功能。我觉得很难看。即使在社区中很常见。这是因为,我们更换了回调地狱承诺,炼狱

我也喜欢在不断的函数的名称,然后短。无论如何,它们只会在本地定义。而且大多数时候,他们会调用其他地方定义的另一个函数(可重复使用)来完成这项工作。我什至只对具有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()。这只是创建已解决承诺的快速方法。我试图通过此方法来实现的是,将我正在运行的所有代码都放在一个位置-那时在。具有描述性更强的名称的所有其他功能都是可重用的。

这种技术的缺点是定义了很多功能。但是,为了避免到处都具有匿名功能,恐怕这是必要的痛苦。还有什么风险:堆栈溢出?(玩笑!)


使用其他答案中定义的数组或对象也可以。这是凯文·里德(Kevin Reid)提出答案

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

使用绑定

如果您想保持函数的可重用性,但又不需要保留其中的内容那么短,则可以使用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() -注意数组的存在-只要有诺言,但要确保所有诺言都不会失败,否则它将停止处理。

而且,除了从args参数定义新变量之外,您还应该可以使用spread()代替then()进行各种出色的工作。


3

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

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

2

无论您从诺言中得到的回报是什么,都会被打包成诺言,以便在下一.then()阶段解开。

当您需要同时返回一个或多个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 */);


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

}

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.