你如何正确地从承诺中返回多个值?
我最近遇到了一些情况,我不知道如何正确解决。 假设下面的代码:
somethingAsync() .then( afterSomething ) .then( afterSomethingElse ) function afterSomething( amazingData ) { return processAsync( amazingData ); } function afterSomethingElse( processedData ) { }
现在可能会出现我想在afterSomethingElse
访问amazingData
的afterSomethingElse
。
一个明显的解决scheme是从afterSomething
返回一个数组或一个哈希值,因为,你只能从一个函数返回一个值。 但是我想知道是否有方法让afterSomethingElse
接受两个参数并且同样调用它,因为这似乎更容易logging和理解。
我只是想知道这种可能性,因为有Q.spread
,它做了类似于我想要的东西。
您无法parsing具有多个属性的承诺,就像无法从函数返回多个值一样 。 一个承诺概念上代表了一段时间的价值,所以虽然你可以表示复合值,你不能把多个值放在承诺。
一个承诺固有地解决一个单一的价值 – 这是如何工作的一部分, 如何承诺/ A +规范的作品,以及如何抽象的作品。
你可以得到最接近的是使用Q.spread
和返回数组,或者如果支持的话使用ES6解构,或者你愿意使用像BabelJS这样的转译工具。
关于将承诺链传递给承诺链,请参阅Bergi的杰出规范 。
你可以返回一个包含两个值的对象 – 这没有什么错。
另一个策略是通过封闭来保持价值,而不是传递:
somethingAsync().then(afterSomething); function afterSomething(amazingData) { return processAsync(amazingData).then(function (processedData) { // both amazingData and processedData are in scope here }); }
完全而不是部分内联的forms(相当于,可以说更一致):
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的解构expression式来获取单个值。
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" })
你可以做两件事,返回一个对象
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的forms出现。
正如你的例子正在运行一些代码,我会假设它是在一个函数中声明的。 我会把它叫做toto() 。 然后我们将有另一个函数,它会在AfterSomething()和afterSomethingElse()之后运行 。
function toto() { return somethingAsync() .then( tata ); }
您还会注意到我添加了一个return语句,因为它通常是与Promise一起使用的方式 – 您总是返回一个promise,以便我们可以在需要时保持链接。 在这里, somethingAsync()会产生amazingData,并且在新函数中的任何位置都可以使用它。
现在这个新的函数看起来通常取决于processAsync()是否也是asynchronous的 ?
processAsync不是asynchronous的
如果processAsync()不是asynchronous的,没有理由过度复杂。 一些旧的好顺序代码会使它。
function tata( amazingData ) { var processed = afterSomething( amazingData ); return afterSomethingElse( amazingData, processed ); } function afterSomething( amazingData ) { return processAsync( amazingData ); } function afterSomethingElse( amazingData, processedData ) { }
请注意,如果afterSomethingElse()正在执行某些asynchronous操作,则无关紧要 。 如果是这样,一个承诺将被退回,链条可以继续。 如果不是,则返回结果值。 但是因为这个函数是从一个then()中调用的,所以这个值将被封装成一个promise(至less在原始的Javascript中)。
processAsyncasynchronous
如果processAsync()是asynchronous的,代码看起来会有些不同。 这里我们考虑afterSomething()和afterSomethingElse()不会在其他地方被重用。
function tata( amazingData ) { return afterSomething() .then( afterSomethingElse ); function afterSomething( /* no args */ ) { return processAsync( amazingData ); } function afterSomethingElse( processedData ) { /* amazingData can be accessed here */ } }
和beforeSomethingElse()一样 。 它可以是asynchronous的或不是。 一个承诺将被退回,或一个价值包装成一个解决的承诺。
你的编码风格与我所用的编码风格非常接近,这就是为什么我在2年后回答的原因。 我不是在任何地方都有匿名function的忠实粉丝。 我觉得很难阅读。 即使在社区中相当普遍。 正如我们用承诺 – 炼狱取代callback – 地狱 。
我也喜欢保留那么短的function名称。 他们只会在本地定义。 而且大多数时候他们会调用其他地方定义的另一个函数 – 可重用 – 来完成这项工作。 我甚至为只有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() 。 这只是一个快速创build一个解决的承诺。 我试图通过这个来实现所有的代码,我在一个地方运行 – 就在这些地方 。 所有其他具有更多描述性名称的函数都是可重用的。
这种技术的缺点是它定义了很多function。 但是,为了避免匿名function,这恐怕是一个必要的痛苦。 什么风险呢?堆栈溢出? (玩笑!)
使用其他答案中定义的数组或对象也可以。 这一点是凯文·里德提出的答案 。
你也可以使用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的列表(第一个除外)join到函数中。
使用Promise.all
在你的文章中,你提到了spread()的使用。 我从来没有使用过你使用的框架,但这里是你应该如何使用它。
有些人认为Promise.all()是所有问题的解决scheme,所以值得一提的是我猜。
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参数中定义新的variables,你应该能够使用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); });