var p1 = new Promise(function(resolve, reject) {
setTimeout(() => resolve("first"), 5000);
});
var p2 = new Promise(function(resolve, reject) {
setTimeout(() => resolve("second"), 2000);
});
var p3 = new Promise(function(resolve, reject) {
setTimeout(() => resolve("third"), 1000);
});
console.log("last to print");
p1.then(()=>p2).then(()=>p3).then(()=> console.log("last to be printed"))
As I was reading about promises, I know that I can print promises synchronous (in this case print: first, second, third, last to print) when I use async /await. Now I have also been reading that the same thing can be achieved using .then chaining and async/await is nothing 'special'. When I try to chain my promises, however, nothing happens except for the console.log of "last to be printed". Any insight would be great! Thanks!!
Edit to question:
var p1 = new Promise(function (resolve, reject) {
setTimeout(() => console.log("first"), 5000);
resolve("first resolved")
});
var p2 = new Promise(function (resolve, reject) {
setTimeout(() => console.log("second"), 2000);
resolve("second resolved")
});
var p3 = new Promise(function (resolve, reject) {
setTimeout(() => console.log("third"), 0);
resolve("third resolved")
});
console.log("starting");
p1.then((val) => {
console.log("(1)", val)
return p2
}).then((val) => {
console.log("(2)", val)
return p3
}).then((val) => {
console.log("(3)", val)
})
Loggs:
starting
(1) first resolved
(2) second resolved
(3) third resolved
third
second
first
1: if executor function passed to new Promise is executed immediately, before the new promise is returned, then why are here promises resolved ()synchronously) first and after the setTimeouts (asynchronously) gets executed?
Return value vs. resolve promise:
var sync = function () {
return new Promise(function(resolve, reject){
setTimeout(()=> {
console.log("start")
resolve("hello") //--works
// return "hello" //--> doesnt do anything
}, 3000);
})
}
sync().then((val)=> console.log("val", val))
Answer
The executor function you pass to new Promise
is executed immediately, before the new promise is returned. So when you do:
var p1 = new Promise(function(resolve, reject) {
setTimeout(() => resolve("first"), 5000);
});
...by the time the promise is assigned to p1
, the setTimeout
has already been called and scheduled the callback for five seconds later. That callback happens whether you listen for the resolution of the promise or not, and it happens whether you listen for resolution via the await
keyword or the then
method.
So your code starts three setTimeouts
immediately, and then starts waiting for the first promise's resolution, and only then waiting for the second promise's resolution (it'll already be resolved, so that's almost immediate), and then waiting for the third (same again).
To have your code execute those setTimeout
calls only sequentially when the previous timeout has completed, you have to not create the new promise until the previous promise resolves (using shorter timeouts to avoid lots of waiting):
console.log("starting");
new Promise(function(resolve, reject) {
setTimeout(() => resolve("first"), 1000);
})
.then(result => {
console.log("(1) got " + result);
return new Promise(function(resolve, reject) {
setTimeout(() => resolve("second"), 500);
});
})
.then(result => {
console.log("(2) got " + result);
return new Promise(function(resolve, reject) {
setTimeout(() => resolve("third"), 100);
});
})
.then(result => {
console.log("(3) got " + result);
console.log("last to print");
});
Remember that a promise doesn't do anything, and doesn't change the nature of the code in the promise executor. All a promise does is provide a means of observing the result of something (with really handy combinable semantics).
Let's factor out the common parts of those three promises into a function:
function delay(ms, ...args) {
return new Promise(resolve => {
setTimeout(resolve, ms, ...args);
});
}
Then the code becomes a bit clearer:
function delay(ms, ...args) {
return new Promise(resolve => {
setTimeout(resolve, ms, ...args);
});
}
console.log("starting");
delay(1000, "first")
.then(result => {
console.log("(1) got " + result);
return delay(500, "second");
})
.then(result => {
console.log("(2) got " + result);
return delay(100, "third");
})
.then(result => {
console.log("(3) got " + result);
console.log("last to print");
});
Now, let's put that in an async
function and use await
:
function delay(ms, ...args) {
return new Promise(resolve => {
setTimeout(resolve, ms, ...args);
});
}
(async() => {
console.log("starting");
console.log("(1) got " + await delay(1000, "first"));
console.log("(2) got " + await delay(500, "second"));
console.log("(3) got " + await delay(100, "third"));
console.log("last to print");
})();
Promises make that syntax possible, by standardizing how we observe asynchronous processes.
Re your edit:
1: if executor function passed to new Promise is executed immediately, before the new promise is returned, then why are here promises resolved ()synchronously) first and after the
setTimeouts
(asynchronously) gets executed?
There are two parts to that question:
A) "...why are here promises resolved ()synchronously) first..."
B) "...why are here promises resolved...after the setTimeouts
(asynchronously) gets executed"
The answer to (A) is: Although you resolve them synchronously, then
always calls its callback asynchronously. It's one of the guarantees promises provide. You're resolving p1
(in that edit) before the executor function returns. But the way you're observing the resolutions ensures that you observe the resolutions in order, because you don't start observing p2
until p1
has resolved, and then you don't start observing p3
until p2
is resolved.
The answer to (B) is: They don't, you're resolving them synchronously, and then observing those resolutions asynchronously, and since they're already resolved that happens very quickly; later, the timer callbacks run. Let's look at how you create p1
in that edit:
var p1 = new Promise(function (resolve, reject) {
setTimeout(() => console.log("first"), 5000);
resolve("first resolved")
});
What happens there is:
new Promise
gets called- It calls the executor function
- The executor function calls
setTimeout
to schedule a callback - You immediately resolve the promise with
"first resolved"
new Promise
returns and the resolved promise is assigned top1
- Later, the timeout occurs and you output
"first"
to the console
Then later you do:
p1.then((val) => {
console.log("(1)", val)
return p2
})
// ...
Since then
always calls its callback asynchronously, that happens asynchronously — but very soon, because the promise is already resolved.
So when you run that code, you see all three promises resolve before the first setTimeout
callback occurs — because the promises aren't waiting for the setTimeout
callback to occur.
You may be wondering why you see your final then
callback run before you see "third"
in the console, since both the promise resolutions and the console.log("third")
are happening asynchronously but very soon (since it's a setTimeout(..., 0)
and the promises are all pre-resolved): The answer is that promise resolutions are microtasks and setTimeout
calls are macrotasks (or just "tasks"). All of the microtasks a task schedules are run as soon as that task finishes (and any microtasks that they schedule are then executed as well), before the next task is taken from the task queue. So the task running your script does this:
- Schedules a task for the
setTimeout
callback - Schedules a microtask to call
p1
'sthen
callback - When the task ends, its microtasks are processed:
- The first
then
handler is run, scheduling a microtask to run the secondthen
handler - The second
then
handler runs and schedules a micro task to call the thirdthen
handler - Etc. until all the
then
handlers have run
- The first
- The next task is picked up from the task queue. It's probably the
setTimeout
callback forp3
, so it gets run and"third"
appears in the console
- Return value vs. resolve promise:
The part you've put in the question doesn't make sense to me, but your comment on this does:
I read that returning a value or resolving a promise is same...
What you've probably read is that returning a value from then
or catch
is the same as returning a resolved promise from then
or catch
. That's because then
and catch
create and return new promises when they're called, and if their callbacks return a simple (non-promise) value, they resolve the promise they create with that value; if the callback returns a promise, they resolve or reject the promise they created based on whether that promise resolves or rejects.
So for instance:
.then(() => {
return 42;
})
and
.then(() => {
return new Promise(resolve => resolve(42));
})
have the same end result (but the second one is less efficient).
Within a then
or catch
callback:
- Returning a non-promise resolves the promise
then
/catch
created with that value - Throwing an error (
throw ...
) rejects that promise with the value you throw - Returning a promise makes
then
/catch
's promise resolve or reject based on the promise the callback returns
No comments:
Post a Comment