In an attempt to understand promises more clearly, i have been reading up a few very interesting articles on the same. I came across the following code which works perfectly for executing promises sequentially. But i am not able to understand how it works.
function doFirstThing(){
return new Promise(function(resolve,reject){
setTimeout(()=>{
resolve(1);
},1000)
})
}
function doSecondThing(res){
return new Promise(function(resolve,reject){
setTimeout(()=>{
resolve(res + 1);
},1000)
})
}
function doThirdThing(res){
return new Promise(function(resolve,reject){
setTimeout(()=>{
resolve(res + 2);
},1000)
})
}
promiseFactories = [doFirstThing, doSecondThing, doThirdThing];
function executeSequentially(promiseFactories) {
var result = Promise.resolve(); // this is the most problematic line
promiseFactories.forEach(function (promiseFactory) {
result = result.then(promiseFactory);// what is happening here ?
});
return result;
}
executeSequentially(promiseFactories)
I do understand that promises are executed as soon as they are created. For some reason i am not able to understand the flow of execution. Especially this following line:
var result = Promise.resolve()//and empty promise is created.
Please if somebody can help me understand how calling the promiseFactory method inside the 'then' method of the empty promise makes it execute sequentially, like so. Or is it because of the forEach loop ?
result = result.then(promiseFactory);
I tried replacing the 'forEach' with a 'map' function and still yielded the same result. i.e, the methods where executed sequentially.
Also, how is the value passed from one chained function to other ?
Any help or article/blog is highly appreciated.
You can image a Promise as a box with execution inside. As far as the promise is created, the execution starts. To get the result value, you have to open the box. You can use then for it:
Promise.resolve(5).then(result => console.log(result)); // prints 5
If you want to chain promises you can do it by opening the box one by one:
Promise.resolve(5)
.then(result => Promise.resolve(result + 1))
.then(result => Promise.resolve(result * 2))
.then(result => console.log(result)); // prints 12
This chaining makes the executions synchronous (one by one).
If you want to execute several promises asynchronously (you don't chain results), you can use Promise.all:
Promise.all([Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)])
.then(result => console.log(result)); // prints [1,2,3]
In your case:
Promise.all(promiseFactories).then(result => console.log(result));
Another option how to work with promises is to await them:
(async ()=> {
var res1 = await Promise.resolve(5);
var res2 = await Promise.resolve(res1 + 1);
var res3 = await Promise.resolve(res2 * 2);
console.log(res3); // prints 12
})();
await works similar to then - it makes asynchronous execution to synchronous.
In your case:
async function executeSequentially(promiseFactories) {
for (const p of promiseFactories) {
const result = await p;
console.log(result);
}
}
Note: await packs a value into a Promise out of the box:
var res1 = await 5; // same as await Promise.resolve(5)
The executeSequentially method returns all the Promises one after each other. It happens to iterate over promiseFactory, but it could be written as:
function executeSequentially(promiseFactories) {
return doFirstThing()
.then(() => doSecondThing())
.then(doThirdThing() );
}
It is just the same. We are basically returning a Promise.
Now, however, we want to iterate over a collection of promises.
When iterating, we need to attach the current Promise to the previous with a then. But the forEach does not expose the next Promise -or the previous- in every iteration. And yet we still need it in order to keep chaining Promises one by one. Hence, the result 'hack':
function executeSequentially(promiseFactories) {
var result = Promise.resolve(); /*We need a thing that keeps yelling
the previous promise in every iteration, so we can keep chaining.
This 'result' var is that thing. This is keeping a Promise in every
iteration that resolves when all the previous promises resolve
sequentially. Since we don't have a Promise in the array
previous to the first one, we fabricate one out of 'thin air'
with Promise.resolve() */
promiseFactories.forEach(function (promiseFactory) {
result = result.then(promiseFactory); /* Here result is update
with a new Promise, with is the result of chaining `result`
with the current one. Since `result` already had all the previous ones,
at the end, `result` will be a Promise that depends upon all the
Promises resolution.*/
});
return result;
}
Now, there's also a syntax quirk that maybe is puzzling you:
result = result.then(promiseFactory);
This line is pretty much the same as the following:
result = result.then(resolvedValue => promiseFactory(resolvedValue));
Please if somebody can help me understand how calling the promiseFactory method inside the 'then' method of the empty promise makes it execute sequentially, like so. Or is it because of the forEach loop ?
First thing first, promiseFactory is a pretty bad name there. The method should be better written as follows:
function executeSequentially(promises) {
var result = Promise.resolve(); // this is the most problematic line
promises.forEach(function (currentPromise) {
result = result.then(currentPromise);// what is happening here ?
});
return result;
}
So:
how calling the currentPromise method inside the 'then' method of the empty promise makes it execute sequentially?
It makes execute sequentially because when you attach a Promise to another by then, it executes sequentially. Is a then thing, it is not at all related to the fact that we are iterating over Promises. With plain Promises outside an iteration it works pretty much the same:
Promise.resolve() // fake Promises that resolves instanly
.then(fetchUsersFromDatabase) // a function that returns a Promise and takes
// like 1 second. It won't be called until the first one resolves
.then(processUsersData) // another function that takes input from the first, and
// do a lot of complex and asynchronous computations with data from the previous promise.
// it won't be called until `fetchUsersFromDatabase()` resolves, that's what
// `then()` does.
.then(sendDataToClient); // another function that will never be called until
// `processUsersData()` resolves
It is always recommended to use Promise.all if you want such behaviour:
function doFirstThing() {
return new Promise(function(resolve, reject) {
setTimeout(() => {
resolve(1);
}, 1000)
})
}
function doSecondThing(res) {
return new Promise(function(resolve, reject) {
setTimeout(() => {
resolve(res + 1);
}, 1000)
})
}
function doThirdThing(res) {
return new Promise(function(resolve, reject) {
setTimeout(() => {
resolve(res + 2);
}, 1000)
})
}
let promiseFactories = [doFirstThing(2), doSecondThing(1), doThirdThing(3)];
Promise.all(promiseFactories)
.then(data => {
console.log("completed all promises", data);
})
To run it sequentially one after another:
function doFirstThing() {
return new Promise(function(resolve, reject) {
setTimeout(() => {
resolve(1);
}, 1000)
})
}
function doSecondThing(res) {
return new Promise(function(resolve, reject) {
setTimeout(() => {
resolve(res + 1);
}, 3000)
})
}
function doThirdThing(res) {
return new Promise(function(resolve, reject) {
setTimeout(() => {
resolve(res + 2);
}, 5000)
})
}
promiseFactories = [doFirstThing, doSecondThing, doThirdThing];
function executeSequentially(promiseFactories) {
promiseFactories.forEach(function(promiseFactory) {
promiseFactory(1).then((data) => {
console.log(data)
});
});
}
executeSequentially(promiseFactories);
If we lay out the foreach loop it will look like the following
function doFirstThing(){
return new Promise(function(resolve,reject){
setTimeout(()=>{
console.log(1);
resolve(1);
},1000)
})
}
function doSecondThing(res){
return new Promise(function(resolve,reject){
setTimeout(()=>{
console.log(2);
resolve(res + 1);
},2000)
})
}
function doThirdThing(res){
return new Promise(function(resolve,reject){
setTimeout(()=>{
console.log(3);
resolve(res + 2);
},3000)
})
}
Promise.resolve()
.then(doFirstThing())
.then(doSecondThing())
.then(doThirdThing());
I do understand that promises are executed as soon as they are created. For some reason i am not able to understand the flow of execution. Especially this following line:
var result = Promise.resolve()//and empty promise is created.
This is just to get hold of the promise chain's starting point. Here it is an already resolved promise. To better understand it you can use one of your promises to get hold of the promise chain like below.
let promiseFactories= [doSecondThing, doThirdThing];
let result = doFirstThing();
promiseFactories.forEach(function (promiseFactory) {
result = result.then(promiseFactory);
});
This will also work.
Related
This question already has answers here:
Any difference between await Promise.all() and multiple await?
(6 answers)
Closed 1 year ago.
This is a basic question, but i couldn't find the answer to it anywhere.
We have two approaches:
// consider someFunction1() and someFunction2() as functions that returns Promises
Approach #1:
return [await someFunction1(), await someFunction2()]
Approach #2:
return await Promise.all([someFunction1(), someFunction2()])
My Team Leader said that both approaches ended up in the same solution (both functions executting in parallel). But, from my knowledge, the first approach would await someFunction1() to resolve and then would execute someFunction2.
So that's the question, is it really the same, or are there any performance improvements on second approach? Proofs are very welcome!
No, you should not accept that:
return [await someFunction1(), await someFunction2()];
Is the same as:
return await Promise.all([someFunction1(), someFunction2()]);
I should also note that await in the above return await is not needed. Check out this blog post to learn more.
They are different!
The first approach (sequential)
Let's determine the difference by inspecting how each of the two alternatives works.
[await someFunction1(), await someFunction2()];
Here, in an async context, we create an array literal. Note that someFunction1 is called (a function which probably returns a new promise each time it gets called).
So, when you call someFunction1, a new promise is returned, which then "locks" the async context because the preceding await.
In a nutshell, the await someFunction1() "blocks" the array initialization until the returned promise gets settled (by getting resolved or rejected).
The same process is repeated to someFunction2.
Note that, in this first approach, the two promises are awaited in sequence. There is, therefore, no similarity with the approach that uses Promise.all. Let's see why.
The second approach (non-sequential)
Promise.all([someFunction1(), someFunction2()])
When you apply Promise.all, it expects an iterable of promises. It waits for all the promises you give to resolve before returns a new array of resolved values, but don't wait each promise resolve until waiting another one. In essence, it awaits all the promises at the same time, so it is a kind of "non-sequential". As JavaScript is single-threaded, you cannot tell this "parallel", but is very similar in the behavior point of view.
So, when you pass this array:
[someFunction1(), someFunction2()]
You are actually passing an array of promises (which are returned from the functions). Something like:
[Promise<...>, Promise<...>]
Note that the promises are being created outside Promise.all.
So you are, in fact, passing an array of promises to Promise.all. When both of them gets resolved, the Promise.all returns the array of resolved values. I won't explain in all details how Promise.all works, for that, I suggest you checking out the documentation.
You can replicate this "non-sequential" approach by creating the promises before using the await. Like so:
const promise1 = someFunction1();
const promise2 = someFunction2();
return [await promise1, await promise2];
While promise1 is being waited, promise2 is already running (as it was created before the first await), so the behavior is similar to Promise.all's.
My Team Leader said that both approaches ended up in the same solution (both functions executting in parallel).
That is incorrect.
But, from my knowledge, the first approach would await someFunction1() to resolve and then would execute someFunction2.
That is correct.
Here is a demonstration
Approach 1:
const delay = (ms, value) =>
new Promise(resolve => setTimeout(resolve, ms, value));
async function Approach1() {
return [await someFunction1(), await someFunction2()];
}
async function someFunction1() {
const result = await delay(800, "hello");
console.log(result);
return result;
}
async function someFunction2() {
const result = await delay(400, "world");
console.log(result);
return result;
}
async function main() {
const start = new Date();
const result = await Approach1();
const totalTime = new Date() - start;
console.log(`result: ${result}
total time: ${totalTime}`);
}
main();
Result is:
hello
world
result: hello,world
total time: 1205
Which means that someFunction1 runs to completion first and then someFunction2 is executed. It is sequential
Approach 2:
const delay = (ms, value) =>
new Promise(resolve => setTimeout(resolve, ms, value));
async function Approach2() {
return await Promise.all([someFunction1(), someFunction2()]);
}
async function someFunction1() {
const result = await delay(800, "hello");
console.log(result);
return result;
}
async function someFunction2() {
const result = await delay(400, "world");
console.log(result);
return result;
}
async function main() {
const start = new Date();
const result = await Approach2();
const totalTime = new Date() - start;
console.log(`result: ${result}
total time: ${totalTime}`);
}
main();
Result is:
world
hello
result: hello,world
total time: 803
Which means that someFunction2 finishes before someFunction1. The two are parallel.
Easy to see the difference
function createTimer(ms, id) {
console.log(`id: ${id} started ${new Date()}`);
return new Promise((res, rej) => {
setTimeout( () => {
console.log(`id: ${id} finished ${new Date()}`);
res(id);
}, ms );
});
}
(async function() {
var result1 = [await createTimer(5000, '1'), await createTimer(5000, '2')];
var result2 = await Promise.all([createTimer(5000, '3'), createTimer(5000, '4')]);
console.log(result1);
console.log(result2);
})();
The first one starts 1 and when 1 finishes it starts 2.
The second one starts 3 and 4 at almost the very same moment.
If you start with a function which simulates doing some work which outputs at stages of that work but takes some time. eg
function someFunction1(){
return new Promise(resolve => {
let i = 0;
const intervalId = setInterval(() => {
i++;
console.log("someFunction1", i);
if(i == 5){
clearInterval(intervalId)
resolve(1);
}
}, 1000);
});
}
And then you duplicate that with a second, similar, method. You plug your 2 methods in and you see that the one using Promise.all does it in parallel but the one using 2 await calls does it in series.
Parallel
function someFunction1(){
return new Promise(resolve => {
let i = 0;
const intervalId = setInterval(() => {
i++;
console.log("someFunction1", i);
if(i == 5){
clearInterval(intervalId)
resolve(1);
}
}, 1000);
});
}
function someFunction2(){
return new Promise(resolve => {
let i = 0;
const intervalId = setInterval(() => {
i++;
console.log("someFunction2", i);
if(i == 5){
clearInterval(intervalId)
resolve(2);
}
}, 1000);
});
}
(async function(){
const result = await Promise.all([someFunction1(),someFunction2()]);
console.log("result",result);
})();
Series
function someFunction1(){
return new Promise(resolve => {
let i = 0;
const intervalId = setInterval(() => {
i++;
console.log("someFunction1", i);
if(i == 5){
clearInterval(intervalId)
resolve(1);
}
}, 1000);
});
}
function someFunction2(){
return new Promise(resolve => {
let i = 0;
const intervalId = setInterval(() => {
i++;
console.log("someFunction2", i);
if(i == 5){
clearInterval(intervalId)
resolve(2);
}
}, 1000);
});
}
(async function(){
const result = [await someFunction1(),await someFunction2()];
console.log("result",result);
})();
Both give the exact same result but getting there is very different.
MDN documentation for Promise.all() states that
This method can be useful for aggregating the results of multiple promises. It is typically used when there are multiple related asynchronous tasks that the overall code relies on to work successfully — all of whom we want to fulfill before the code execution continues.
While it isn't explicit, you can await Promise.all to track multiple promises. Only when all promises are resolved will the code execution continue.
The other approach you mention of capturing separate asynchronous tasks in an array is not the same due to how await operates.
An await splits execution flow, allowing the caller of the async function to resume execution. After the await defers the continuation of the async function, execution of subsequent statements ensues. If this await is the last expression executed by its function, execution continues by returning to the function's caller a pending Promise for completion of the await's function and resuming execution of that caller.
So, each await will pause execution before resuming. No need for a demonstration.
Hello Stack Overflow community,
I come to you with a problem related to JS async/await. I am trying to call an async function and then log the array to where the async function pushes the results to the console. If I call it like so directly in the console:
console.log(Page.data) - I can see that it has results in it, but if it is called on click of a button it logs an empty array.
// It is a nested object so do not worry if you don't exactly understand where Page.data comes from
Page.data = []
async function f1() {
// Fetch JSON data
// Process data
// Pushes at some point to the Page.data array
}
async function f2() {
// Fetch JSON data
// Process data
// Pushes at some point to the Page.data array
}
async function f3() {
// Fetch JSON data
// Process data
// Pushes at some point to the Page.data array
}
async function load(loader) {
let fn = async function() {};
if(condition1) fn = f1;
else if(condition2) fn = f2;
else fn = f3;
// This is the line that makes me problems
// According to documentation async functions return a promise
// So why would the array in the case be empty?
// Since I am telling it to display after the function is done
await fn(loader).then(console.log(Page.data))
}
This is just a template of my code and logic. I hope that you can understand where I am going.
Your help will be much appreciated.
The await expression causes async function execution to pause until a Promise is settled (that is, fulfilled or rejected), and to resume execution of the async function after fulfillment. When resumed, the value of the await expression is that of the fulfilled Promise.
For instance (this is an MDN example with some added comments):
function resolveAfter2Seconds(x) {
return new Promise(resolve => {
setTimeout(() => {
resolve(x);
}, 2000);
});
}
async function f1() {
// note that here, you are "awaiting" the RESOLVED RESULT of the promise.
// there is no need to "then" it.
var x = await resolveAfter2Seconds(10);
// the promise has now already returned a rejection or the resolved value.
console.log(x); // 10
}
f1();
So you would "await" your function, which would hold up the execution until the promise either resolves or rejects. After that line, you would run your console.log, and it would log as expected. Short answer, "remove the then".
I should add, if the result of the "awaited" function is not a promise, it is converted to a promise (so technically, there is no need to return a promise, it'll wrap up your returned value for you).
the problem is that you can use then with await for the same method, lets check some examples provided by MDN:
this is a working Promise using async/await:
let myFirstPromise = new Promise((resolve, reject) => {
setTimeout(function() {
resolve("Success!");
}, 250)
})
const functionCaller = async() => {
const result = await myFirstPromise
console.log("the result: ", result);
}
functionCaller();
what you are trying is:
let myFirstPromise = new Promise((resolve, reject) => {
setTimeout(function() {
resolve("Success!");
}, 250)
})
const functionCaller = async() => {
// you are not returning anything here... actually you are not doing anything with the response. despite it shows something, it is not sending any response
await myFirstPromise.then(console.log("something happened"))
}
// then this function doesn't really get a value.
functionCaller();
so what you need to do in your load call, is to change it like this:
async function load(loader) {
let fn = async function() {};
if(condition1) fn = f1;
else if(condition2) fn = f2;
else fn = f3;
return await fn(loader)
}
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.
I am trying to wrap my head around the incredibly confusing topics of Promises in Javascript.
One of the doubts that I encountered is that what happens when a then() callback actually returns a promise.
Look at the code below taken from javascript.info
new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000);
}).then(function(result) {
alert(result); // 1
//Actually resolves before calling the next then()
return new Promise((resolve, reject) => { // (*)
setTimeout(() => resolve(result * 2), 1000);
});
}).then(function(result) { // (**)
alert(result); // 2
return new Promise((resolve, reject) => {
setTimeout(() => resolve(result * 2), 1000);
});
}).then(function(result) {
alert(result); // 4
});
Anyone of the inner promises is actually fulfilled before moving on to the next then statement.
Why is that? How does Javascript know when the returned value is a promise itself?
Is there really a check within the JS interpreter for this? Like something like this-:
if(ret_value instanceof Promise) {
wait_for_promise(ret_value);
}
This does not really make sense intuitively. You would think that the return value will be kept as is, ie the next then() in the chain would receive a promise and not the result of the promise.
I come from a Java background which is probably why this loose typing is irritating me.
The actual check that is performed is against thenables, not only against Promises.
If the returned value of a Promise resolver has a .then method, this method will be called, and the next resolver/rejecter will receive this method's value as argument:
const thenable = { // simple object, not an instance of Promise per se
then: (res, rej) => setTimeout(() => res('from thenable'), 1000)
};
Promise.resolve(1)
.then(v => thenable)
.then(console.log)
Promise.resolve()
.then(() => {
return new Promise(res => setTimeout(() => res(1),1000))
})
.then(v => {
console.log(v); //1
});
Take a look at the second then in the example above: I passed in a callback to log the value. Because a Promise was returned in the first then, JavaScript will not execute the callback in the second then until the Promise returned from the first then is resolved.
If, instead, in the first then I passed back a value (below), instead of a Promise, JavaScript would execute the callback I passed into the second then immediately.
Promise.resolve()
.then(() => {
return 1
})
.then(v => {
console.log(v); //1
});
Why is that? How does Javascript know when the returned value is a promise itself?
Maybe this can illustrate an answer to your question:
let x = Promise.resolve();
console.log(x instanceof Promise); //true - JavaScript "knows" that x is a promise
let y = 1;
console.log(y instanceof Promise); //false
In my opinion #kaiido s answer is the one You're searching for.
Simplified, an implementation of a promise internally could do a check like this one:
if( typeof(retValue.then)==="function" ) {
retValue.then( yourCallback );
}else{
yourCallback( retValue );
}
Wrote this on mobile phone. Please excuse my formatting.
I have a working promise chain:
function startSync(db) {
var promise = new Promise(function(resolve, reject) {
syncCats(db)
.then(syncTrees(db))
.then(syncCars(db))
...
.then(resolve());
});
return promise;
}
This works great. It performs each of those function calls, waiting for each one to complete before firing off another. Each of those functions returns a promise, like so:
function syncCaesar(db) {
var promise = new Promise(resolve, reject) {
// do aysnc db calls, and whatnot, resolving/rejecting appropriately
}
return promise;
}
I need to run this chain on a series of databases, sequentially.
I've tried other solutions here, but they would fire off syncCats() all at once.
For instance:
var promise = Promise.resolve();
dbs.forEach(function(db) {
promise = promise.then(startSync(db));
}
Fires off syncCats(db[0]) and syncCats(db[1]) simultaneously.
Edit:
Performing startSync(dbs[0]).then(startSync(dbs[1])); acts the same.
Edit2: Also performs the same:
dbs.forEach(function(db) {
promise = promise.then(function() {
return startSync(db);
}
}
You're calling startSync and then passing its return value in to then. So naturally, if you do that twice, it's going to start the process twice in parallel.
Instead, pass in a function that doesn't call startSync until it's called:
var promise = Promise.resolve();
dbs.forEach(function(db) {
promise = promise.then(function() { startSync(db); });
});
or with ES2015:
let promise = Promise.resolve();
dbs.forEach(function(db) {
promise = promise.then(_ => startSync(db));
});
Separately, three things jump out about startSync:
It starts all its operations in parallel, not sequentially
It exhibits the promise creation anti-pattern. There's no reason for startSync to create a new promise; it already has a promise it can work with
It ensures that its resolution value is undefined
If you really want the operations running in parallel like that, I suggest being more explicit about it:
function startSync(db) {
return Promise.all([
syncCats(db),
syncTrees(db),
syncCars(db)
])
.then(_ => undefined); // This does #3
}
...but you could also do to avoid the anti-pattern:
// Still run in parallel!
function startSync(db) {
return syncCats(db)
.then(syncTrees(db))
.then(syncCars(db))
.then(_ => undefined); // Does #3
}
If you meant for them to be sequential, you need to pass functions, not the result of calling them:
function startSync(db) {
return syncCats(db)
.then(_ => syncTrees(db))
.then(_ => syncCars(db))
.then(_ => undefined); // Again, does #3
}
If you made syncCats, syncTrees, and syncCars resolve their promises with db, like this:
function syncCats(db) {
return startSomethingAsync().then(_ => db);
}
...then it could be:
function startSync(db) {
return syncCats(db)
.then(syncTrees)
.then(syncCars)
.then(_ => undefined); // Only here for #3
}
...since each would receive db as its first argument.
Finally, if you don't need to force undefined as the promise resolution value, I suggest dropping that part from the above. :-)
With appreciation to T.J and Jaromanda, I was able to solve it by fixing how startSync() was resolved:
function startSync(db) {
var promise = new Promise(function(resolve, reject) {
syncCats(db)
.then(syncTrees(db)) // not correct way to call, see below
.then(syncCars(db))
...
.then(function() { resolve(); });
});
return promise;
}
Now, as T.J points out, the way each of the .then's is being called is incorrect. He explains it better in his answer. I'm being saved by some misunderstood database layer queueing. syncTrees() and syncCars() should be running in parallel.
If I understood it correctly, you will be having an array of databases and you want to sync them one by one i.e. sync for dbs[0], once that is complete sync for dbs[1], and so on. If that's correct you can do something like this.
var syncMultipleDBs = (dataBases) {
var db = dataBases.shift();
if (!db) {
return;
}
startSync(db).then(syncMultipleDBs.bind(null, dataBases));
};
syncsyncMultipleDBs(dbs.slice());
I hope this will help.