Let's say I have a function like this
const fn = () => {
return work()
.then((finalResult) => { // then 1
finish(finalResult);
});
};
const work = () => {
return Promise.resolve(1)
.then(() => { // then 2
return process1();
}).then((result) => { // then 3
return process2(result);
});
};
My question is: can I rely on the fact that finish will be called AFTER process1 and process2. Specifically, is then 1 attached to the promise returned by Promise.resolve(1) or is it attached to the promise returned by then 3.
This is foremost a matter of syntax. What you are doing is equivalent to
const fn = () => {
const promiseA = work();
const promiseB = promiseA.then(finish); // then 1
return promiseB;
};
const work = () => {
const promiseC = Promise.resolve(1);
const promiseD = promiseC.then(process1); // then 2
const promiseE = promiseD.then(process2); // then 3
return promiseE;
};
just introducing some extra variables (and also I've simplified the function expressions).
Now we can clearly see that the result of work() is what is returned by it, so promiseA === promiseE. So yes, finish is chained to the result of the "then3" call.
Knowing that then creates a new promise (for the eventual result of the callback) instead of returning the original one, we can also conclude that promiseE !== promiseC, so finish is not chained onto Promise.resolve(1).
Each then will await its preceding promise and then run in turn.
If the preceding promise returns a value, the argument passed to the callback will be that value, or if it returns another promise, the argument to the callback will be the value returned from that promise.
Section 2.2.6.1 of the promise plus specification states:
If/when promise is fulfilled, all respective onFulfilled callbacks must execute in the order of their originating calls to then.
It is guaranteed that finish(finalResult) will run with the result of process2(result) (unless there's an error, of course.)
I found this article to be extremely enlightening on getting to grips with the guts of promises.
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.
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.
Js is not my first language, by a long chalk. I code in 5 or 6 others, and am comfortable with callbacks, which means that I should be able to grok promises.
However, I inherited a node.js project with something like this (simplified), which makes me uncertain)
let promise = someFunction.then({return xxxx;});
// lots of more code & promises
Promises.all().then(() => {return somethingElse});
I am comfortable with lots of promises and Promises.all() to wait for them all to resolve (although I am replacing all .then with await for cleaner code), BUT ...
that return inside a .then disturbs me. If I understand correctly, .then just wraps an async callback - which could happen at any time, even before the rest of the code and that Promises.all, leaving some of the code unexecuted.
Am I correct to be concerned, or is there something that I am missing?
Tl;dr - is it ok to return inside a .then?
Returning inside .then is extremely common and fine to do. What it does is it produces a Promise that now resolves to the value returned (rather than to the value returned by the previous Promise). Example:
// resolves to 1
const prom1 = Promise.resolve(1);
// chains off of prom1, resolves to 2
const prom2 = prom1.then(() => {
return 2;
});
prom1.then(console.log);
setTimeout(() => {
prom2.then(console.log);
});
This technique is very useful when you need to pass along a value from a prior .then to a subsequent .then.
const makeApiCall = num => Promise.resolve(num + 3);
Promise.resolve(1)
.then((result1) => {
return makeApiCall(result1);
})
.then((result2) => {
console.log(result2);
});
What the return will do in your situation specifically:
let promise = someFunction.then({return xxxx;});
// lots of more code & promises
Promises.all().then(() => {return somethingElse});
If promise is passed to the Promise.all, then its resolve value will be xxxx, rather than whatever someFunction resolves to. (If the Promise.all's .then ignores its argument, then it does nothing except wait for the returned value to resolve, if it's a Promise.)
const someFunction = () => Promise.resolve(1);
let promise = someFunction().then(() => {
return 'xxxx';
});
Promise.all([promise]).then((allResults) => {
console.log('allResults', allResults);
return 'somethingElse';
})
.then((result2) => {
console.log('Next `.then`', result2);
});
Note that your current code has a number of syntax issues you need to correct.
I'm so much confused about what happens behind the scenes when promise is produced and consume. Please clarify my points and sorry for my weak English.
blank object is created with new keyword Promise constructor is
called and new keyword set the this of Promise constructor points to
the blank object this = blankobject.
Promise constructor receives callback (executor function) in argument
and calls the executor function.
executor function receives two callbacks (resolve,reject) as arguments
setTimeout gets called in the executor function and setTimeOut is
async code
async code goes to background and then Promise constructor returns
Promise object formerly blank object and Promise object reference
saved to myPromise.
a variable is created
What happens next ? When then method is called the code of then method goes to background? I imagine it goes to background and a variable is console.log // 10
After main code execution finishes, async code start setTimeout callback begins to execute and after execution finishes promise is fulfilled and resolved function returns value. How is this value stored in promise object and what happens in then method ?
let myPromise = new Promise (
(resolve, reject) => {
setTimeout(() => {
console.log(getIDs)
resolve(10);
}, 1500);
}
)
let a = 10
myPromise.then(val => {
console.log(val);
})
console.log(a)
The following is a simplified implementation of the built-in Promise class. catch and finally have not been implemented.
The function supplied to the Promise constructor is called the executor function, and is invoked immediately and synchronously.
Every promise has a method .then, enabling the chaining of promises.
Functions supplied to .then are always invoked asynchronously on a microtask (note use of queueMicrotask below).
Every time .then is called, a new promise is created and returned.
.then can be called more than once on the same promise, creating a multicast of the result of the promise, and a branching of the promise chain.
A promise can be in one of three states: pending, fulfilled, or rejected. State transitions are unidirectional: you cannot move from fulfilled or rejected, back to pending.
If a promise is resolved with another promise, then the two promise chains are joined and the outer promise takes on the status of the inner promise (which could be pending), until the inner promise resolves.
function Promise(executor) {
if (!executor) throw "Promise executor undefined"
let status = "pending", value, thenQ = []
const then = onFulfilled => {
let resolver
// This ensures control does not move to later promises
// until prior promises have been resolved.
const nextPromise = new Promise(resolve => (resolver = resolve))
// More than one "then" can be registered with each promise.
thenQ.push((...args) => resolver(onFulfilled(...args)))
return nextPromise
}
// We check if the result is a "thenable"; if so, we treat
// it as an inner promise, otherwise we simply fulfil with
// the result.
const resolve = result => result?.then ? result.then(fulfil) : fulfil(result)
// When a promise has been fulfilled, its "thens" can be run.
const fulfil = result => (status = "fulfilled", value = result, executeThens(value))
// "Thens" are run asynchronously, on a microtask.
const executeThens = value => queueMicrotask(() => thenQ.forEach(el => el(value)))
// The executor is run synchronously.
executor(resolve)
return {
then,
get status() { return status },
get value() { return value }
}
}
// Chaining
new Promise(resolve => {
console.log('Waiting for step 1...')
setTimeout(() => resolve("One, two..."), 1500)
})
.then(result => new Promise(resolve => {
console.log('Waiting for step 2...')
setTimeout(() => resolve(`${result}three, four`), 1500)
}))
.then(result => console.log(`Chaining result: ${result}.`))
// Branching
const p = new Promise(resolve => {
console.log('Waiting for step a...')
setTimeout(() => resolve("Alpha, Bravo..."), 1500)
})
p.then(result => new Promise(resolve => {
console.log('Waiting for step b1...')
setTimeout(() => resolve(`${result}Charlie, Delta`), 1500)
})).then(console.log)
p.then(result => {
console.log('Waiting for step b2...')
return `${result}Echo, Foxtrot`
}).then(console.log)
See also.
I'll go through your code in the order of execution.
At all times the value of this is whatever it was at the beginning. That's because you're only using arrow functions. But that's not relevant since you're not referencing this.
Main Code
let myPromise = new Promise(executor); creates a pending promise object. While creating the promise, the executor function will be executed.
setTimeout(callback, 1500); puts the callback function on some internal timer queue. The javascript engine promises to do its best to execute callback after (at least) 1500ms.
let a = 10; sets the variable a to 10.
myPromise.then(onFulfilled); creates another pending promise. It is linked to myPromise so that onFulfilled will be scheduled asynchronously when myPromise is fulfilled.
console.log(a); prints the value of a which is 10.
For the next 1500ms nothing happens. Then callback gets executed.
callback of setTimeout
console.log(getIDs); prints getIDs. From the name you can guess it's a function. So something like [Function: getIDs] will be printed.
resolve(10); fulfills myPromise and sets its result to 10. Since myPromised is now fulfilled, onFulfilled of anotherPromise gets scheduled asynchronously.
Now we have to wait for the call stack to process. After that, onFulfilled will be called.
onFulfilled of myPromise.then
console.log(val); prints the content of val. That is, the result of myPromise.
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.