I do realise that when returning a non-promise in a .then() handler, it is immediately passed on to the next handler, if it's a promise that is returned, executing is halted for the promise to resolve before it is passed on to the next handler.
Also I know that only one value can be returned from a promise.
That beeing said, how would I go about returning multiple parameters from one .then() handler to the next one? Esepcailly if it's a mix of promises and non-promises. Currently I put everything into a custom object, return it, and use async await in the following then() handler for the promises to reslolve.
Then is use the resolved promise values and the non-promise value to do some work together.
This works fine but my gut is saying that this is somehow not the way it is supposed to be... maybe?
Example:
const current = 'blah';
const previous = 'blubb';
this.doSomeAsyncWork()
.then(
result => {
const nonPromiseValue = new domSomethingSynchronous(current, previous);
// "custom object, mix of promises and non-promises"
return {
nonPromise: nonPromise,
promiseA: ControllerA.asyncOperationA(current, nonPromiseValue.someProperty),
promiseB: ControllerB.asyncOperationB(nonPromiseValue.someOtherProperty),
}
}
)
.then(
async x => {
const nonPromiseValue = x.nonPromiseValue;
const valueA = await x.promiseA;
const valueB = await x.promiseB;
// do something with the results of those three variables
}
)
.catch(
// ...
)
Use return Promise.all on the array of Promises and non-Promises at the end of a .then, and then you can destructure the results immediately in the next .then, no await nor async needed.
The Promise.all will resolve once all Promises in the array have resolved. The non-Promises passed to it will just be passed to the next .then.
const makeProm = () => new Promise(resolve => setTimeout(resolve, 1000, 'resolveValue'));
Promise.resolve()
.then(() => {
const prom = makeProm();
const otherValue = 'foo';
return Promise.all([prom, otherValue]);
})
.then(([resolveValue, otherValue]) => {
console.log(resolveValue, otherValue);
});
Related
There is quite some topics posted about how async/await behaves in javascript map function, but still, detail explanation in bellow two examples would be nice:
const resultsPromises = myArray.map(async number => {
return await getResult(number);
});
const resultsPromises = myArray.map(number => {
return getResult(number);
});
edited: this if of course a fictional case, so just opened for debate, why,how and when should map function wait for await keyword. solutions how to modify this example, calling Promise.all() is kind of not the aim of this question.
getResult is an async function
The other answers have pretty well covered the details of how your examples behave, but I wanted to try to state it more succinctly.
const resultsPromises = myArray.map(async number => {
return await getResult(number);
});
const resultsPromises = myArray.map(number => {
return getResult(number);
});
Array.prototype.map synchronously loops through an array and transforms each element to the return value of its callback.
Both examples return a Promise.
async functions always return a Promise.
getResult returns a Promise.
Therefore, if there are no errors you can think of them both in pseudocode as:
const resultsPromises = myArray.map(/* map each element to a Promise */);
As zero298 stated and alnitak demonstrated, this very quickly (synchronously) starts off each promise in order; however, since they're run in parallel each promise will resolve/reject as they see fit and will likely not settle (fulfill or reject) in order.
Either run the promises in parallel and collect the results with Promise.all or run them sequentially using a for * loop or Array.prototype.reduce.
Alternatively, you could use a third-party module for chainable asynchronous JavaScript methods I maintain to clean things up and--perhaps--make the code match your intuition of how an async map operation might work:
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
const getResult = async n => {
await delay(Math.random() * 1000);
console.log(n);
return n;
};
(async () => {
console.log('parallel:');
await AsyncAF([1, 2, 3]).map(getResult).then(console.log);
console.log('sequential:');
await AsyncAF([1, 2, 3]).series.map(getResult).then(console.log)
})();
<script src="https://unpkg.com/async-af#7.0.12/index.js"></script>
async/await is usefull when you want to flatten your code by removing the .then() callbacks or if you want to implicitly return a Promise:
const delay = n => new Promise(res => setTimeout(res, n));
async function test1() {
await delay(200);
// do something usefull here
console.log('hello 1');
}
async function test2() {
return 'hello 2'; // this returned value will be wrapped in a Promise
}
test1();
test2().then(console.log);
However, in your case, you are not using await to replace a .then(), nor are you using it to return an implicit Promise since your function already returns a Promise. So they are not necessary.
Parallel execution of all the Promises
If you want to run all Promises in parallel, I would suggest to simply return the result of getResult with map() and generate an array of Promises. The Promises will be started sequentially but will eventually run in parallel.
const resultsPromises = indicators.map(getResult);
Then you can await all promises and get the resolved results using Promise.all():
const data = [1, 2, 3];
const getResult = x => new Promise(res => {
return setTimeout(() => {
console.log(x);
res(x);
}, Math.random() * 1000)
});
Promise.all(data.map(getResult)).then(console.log);
Sequential execution of the Promises
However, if you want to run each Promise sequentially and wait for the previous Promise to resolve before running the next one, then you can use reduce() and async/await like this:
const data = [1, 2, 3];
const getResult = x => new Promise(res => {
return setTimeout(() => {
console.log(x);
res(x);
}, Math.random() * 1000)
});
data.reduce(async (previous, x) => {
const result = await previous;
return [...result, await getResult(x)];
}, Promise.resolve([])).then(console.log);
Array.prototype.map() is a function that transforms Arrays. It maps one Array to another Array. The most important part of its function signature is the callback. The callback is called on each item in the Array and what that callback returns is what is put into the new Array returned by map.
It does not do anything special with what gets returned. It does not call .then() on the items, it does not await anything. It synchronously transforms data.
That means that if the callback returns a Promise (which all async functions do), all the promises will be "hot" and running in parallel.
In your example, if getResult() returns a Promise or is itself async, there isn't really a difference between your implementations. resultsPromises will be populated by Promises that may or may not be resolved yet.
If you want to wait for everything to finish before moving on, you need to use Promise.all().
Additionally, if you only want 1 getResults() to be running at a time, use a regular for loop and await within the loop.
If the intent of the first code snippet was to have a .map call that waits for all of the Promises to be resolved before returning (and to have those callbacks run sequentially) I'm afraid it doesn't work like that. The .map function doesn't know how to do that with async functions.
This can be demonstrated with the following code:
const array = [ 1, 2, 3, 4, 5 ];
function getResult(n)
{
console.log('starting ' + n);
return new Promise(resolve => {
setTimeout(() => {
console.log('finished ' + n);
resolve(n);
}, 1000 * (Math.random(5) + 1));
});
}
let promises = array.map(async (n) => {
return await getResult(n);
});
console.log('map finished');
Promise.all(promises).then(console.log);
Where you'll see that the .map call finishes immediately before any of the asynchronous operations are completed.
If getResult always returns a promise and never throws an error then both will behave the same.
Some promise returning functions can throw errors before the promise is returned, in this case wrapping the call to getResult in an async function will turn that thrown error into a rejected promise, which can be useful.
As has been stated in many comments, you never need return await - it is equivalent to adding .then(result=>result) on the end of a promise chain - it is (mostly) harmless but unessesary. Just use return.
This would resolve all promises
const promises = files.map(filename => getPdfToPrint(`output\\${outputDirectory}\\` , filename.replace("/", "")));
const res = await Promise.all(promises)
but now the files.map function should map the filename to an object like this
const promises = files.map(filename => { return {status: getPdfToPrint(`output\\${outputDirectory}\\` , filename.replace("/", "")), filename: filename}});
const res = await Promise.all(promises)
in order to check whether the promise resolved "success" so that I know which files were retrieved and printed. But in this way the Promise resolving the status would still be pending.
How would I solve this problem?
If I understand correctly, your getPdfToPrint() returns a promise. At the moment you're mapping each filename to the promise returned by getPdfToPrint(), but instead you want to have the resolved value in an object at the key status along with the filename. To achieve that, you can make your .map() callback async. This will do two things: firstly, it will enable you to await the getPdfToPrint() function Promise to get the resolved value so you can use that inside of the object you're returning. Secondly, it will make it so that your callback function returns a Promise (as all async functions return a Promise). This will allow you to use Promise.all() to detect once all the promises have resolved:
const promises = files.map(async filename => {
const pdf = await getPdfToPrint(`output\\${outputDirectory}\\` , filename.replace("/", ""));
return {status: pdf, filename};
});
const res = await Promise.all(promises);
I do realise that when returning a non-promise in a .then() handler, it is immediately passed on to the next handler, if it's a promise that is returned, executing is halted for the promise to resolve before it is passed on to the next handler.
Also I know that only one value can be returned from a promise.
That beeing said, how would I go about returning multiple parameters from one .then() handler to the next one? Esepcailly if it's a mix of promises and non-promises. Currently I put everything into a custom object, return it, and use async await in the following then() handler for the promises to reslolve.
Then is use the resolved promise values and the non-promise value to do some work together.
This works fine but my gut is saying that this is somehow not the way it is supposed to be... maybe?
Example:
const current = 'blah';
const previous = 'blubb';
this.doSomeAsyncWork()
.then(
result => {
const nonPromiseValue = new domSomethingSynchronous(current, previous);
// "custom object, mix of promises and non-promises"
return {
nonPromise: nonPromise,
promiseA: ControllerA.asyncOperationA(current, nonPromiseValue.someProperty),
promiseB: ControllerB.asyncOperationB(nonPromiseValue.someOtherProperty),
}
}
)
.then(
async x => {
const nonPromiseValue = x.nonPromiseValue;
const valueA = await x.promiseA;
const valueB = await x.promiseB;
// do something with the results of those three variables
}
)
.catch(
// ...
)
Use return Promise.all on the array of Promises and non-Promises at the end of a .then, and then you can destructure the results immediately in the next .then, no await nor async needed.
The Promise.all will resolve once all Promises in the array have resolved. The non-Promises passed to it will just be passed to the next .then.
const makeProm = () => new Promise(resolve => setTimeout(resolve, 1000, 'resolveValue'));
Promise.resolve()
.then(() => {
const prom = makeProm();
const otherValue = 'foo';
return Promise.all([prom, otherValue]);
})
.then(([resolveValue, otherValue]) => {
console.log(resolveValue, otherValue);
});
I have next code:
contents.map((content) => {
const element = Object.assign({}, content);
if (element.type === 'blabla') {
element.data.id = await getId();
}
return element;
});
But it fails with next error:
The 'await' operator can only be used in an 'async' function
Make each map function async and use Promise.all():
await Promise.all(contents.map(async content => ...);
The Promise.all() method takes an iterable of promises as an input, and returns a single Promise that resolves to an array of the results of the input promises. This returned promise will resolve when all of the input's promises have resolved, or if the input iterable contains no promises. It rejects immediately upon any of the input promises rejecting or non-promises throwing an error, and will reject with this first rejection message / error.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
Note that this means all content items are processed in parallel. If you want sequential execution, use for ... of instead. Each iteration may await as needed.
Just add async in starting of your function.
You cant use await without async.
contents.map(async (content) => {
const element = Object.assign({}, content);
if (element.type === 'blabla') {
element.data.id = await getId();
}
return element;
});
You have to provide whole code for correct result, i just wanted to tell you the mistake you are doing.
I do realise that when returning a non-promise in a .then() handler, it is immediately passed on to the next handler, if it's a promise that is returned, executing is halted for the promise to resolve before it is passed on to the next handler.
Also I know that only one value can be returned from a promise.
That beeing said, how would I go about returning multiple parameters from one .then() handler to the next one? Esepcailly if it's a mix of promises and non-promises. Currently I put everything into a custom object, return it, and use async await in the following then() handler for the promises to reslolve.
Then is use the resolved promise values and the non-promise value to do some work together.
This works fine but my gut is saying that this is somehow not the way it is supposed to be... maybe?
Example:
const current = 'blah';
const previous = 'blubb';
this.doSomeAsyncWork()
.then(
result => {
const nonPromiseValue = new domSomethingSynchronous(current, previous);
// "custom object, mix of promises and non-promises"
return {
nonPromise: nonPromise,
promiseA: ControllerA.asyncOperationA(current, nonPromiseValue.someProperty),
promiseB: ControllerB.asyncOperationB(nonPromiseValue.someOtherProperty),
}
}
)
.then(
async x => {
const nonPromiseValue = x.nonPromiseValue;
const valueA = await x.promiseA;
const valueB = await x.promiseB;
// do something with the results of those three variables
}
)
.catch(
// ...
)
Use return Promise.all on the array of Promises and non-Promises at the end of a .then, and then you can destructure the results immediately in the next .then, no await nor async needed.
The Promise.all will resolve once all Promises in the array have resolved. The non-Promises passed to it will just be passed to the next .then.
const makeProm = () => new Promise(resolve => setTimeout(resolve, 1000, 'resolveValue'));
Promise.resolve()
.then(() => {
const prom = makeProm();
const otherValue = 'foo';
return Promise.all([prom, otherValue]);
})
.then(([resolveValue, otherValue]) => {
console.log(resolveValue, otherValue);
});