Passing intermediate data between promises in JS without using Promise.all and Promise.resolve
I'm kind of a JS noob, but so far I really like ES6 / React / Immutable being able to do functional programming and FRP, and in particular the api promise. I especially like the chain template .then
, for example somePromiseGenerator().then(...).then(...).catch(...)
. This is ideal when the flow of asynchronous calls is perfectly linear. However, I often want to pass results from the top of this chain to the end.
The suboptimal template I used looks like this:
somePromiseGenrator()
.then(r => {
const inter = makeSomeIntermediateResults(r);
const newPromise = makeNewPromise(r);
return Promise.all([newPromise, Promise.resolve(inter)]);
})
.then(r => {
handleR0andR1(r[0], r[1]);
})
This happens, for example, when I receive data from ElasticSearch and want to supplement it with things that are in SQL or Neo4J, or call a secondary API.
Using Promise.all
and especially Promise.resolve
seems like a waste of effort. Is there a better way to make this work?
source to share
Using array destruction to denote variables:
somePromiseGenrator()
.then(r => {
const inter = makeSomeIntermediateResults(r);
const newPromise = makeNewPromise(r);
return Promise.all([newPromise, inter]);
})
.then([newPromiseResult, intermediateResult]) => {
handleR0andR1(newPromiseResult, intermediateResult);
})
Only this is much clearer and I also removed the redundant Promise.resolve()
, since the values ββwithout promises in the passed array before Promise.all()
will be automatically wrapped in this already.
Using / : async
await
Assuming your version of Node.js >= 7.6
(or you are using transpiler / bundler / polyfill that supports ES2016 features), you can convert the following:
function promiseChain() {
return somePromiseGenrator()
.then(r => {
const inter = makeSomeIntermediateResults(r);
const newPromise = makeNewPromise(r);
return Promise.all([newPromise, inter]);
})
.then([newPromiseResult, intermediateResult]) => {
return handleR0andR1(newPromiseResult, intermediateResult);
})
}
in it:
async function promiseChain() {
const r = await somePromiseGenerator();
// I'm assuming this isn't a promise
// since it was initially wrapped in Promise.resolve()
const inter = makeSomeIntermediateResults(r);
const newPromiseResult = await makeNewPromise(r);
return handleR0andR1(newPromiseResult, inter);
}
source to share
I think you can do something like this if you don't want to use Promise.all
:
function pack(){
var args = arguments;
if((args.length - 1) % 2 != 0){
throw "Invalid input";
}
var obj = args[0],
promises = [];
for(var i = 1; i < args.length; i+=2){
let promise = args[i],
name = args[i+1];
promises.push(promise.then(val => {
obj[name] = val;
}));
}
return Promise.all(promises).then(() => obj);
}
Then you can use it like this:
Promise
.resolve({})
.then(obj => {
return pack(obj, genVal("a"), "a", genVal("b"), "b");
})
.then(obj => {
return pack(obj, genVal("c"), "c");
})
.then(console.log);
/* {
partA: "some value from part A",
partB: "some value from part B",
partC: "some value from part C"
} */
source to share
You can define inter
as a variable that has an extended scope, eg. make it a parameter in an expression that is called directly:
(inter => somePromiseGenerator()
.then(r => {
inter = makeSomeIntermediateResults(r);
return makeNewPromise(r);
})
.then(r => handleR0andR1(r, inter))
)();
See also the options suggested in this accepted answer , of which mine is option 2.
source to share