TL;DR: Will already resolved promises always beat setImmediate in a race?
Background:
Sometimes you want to know if a promise is resolved without awaiting its completion. There are some old legacy tricks like using util.inspect to get the internal state and checking if it contains the string "<pending>". However, a more stable solution is to use Promise.race() to essentially wait for your unknown promise with a (very small) timeout.
const p = doSomething();
const result = await Promise.race([
p,
new Promise(resolve => setTimeout(resolve, 1))
]);
if (result) {
// p was resolved within 1 ms!
...
}
This will at most wait 1 ms to get result, which will then either contain the resolved value of p or undefined.
If required, the "timeout promise" may of course return something different than undefined to distinguish actual undefined values returned from doSomething():
const PENDING = Symbol.for('PENDING');
const result = await Promise.race([
p,
new Promise(resolve => setTimeout(() => resolve(PENDING), 1))
]);
if (result !== PENDING) {
...
}
Now we'll either get the resolved value from doSomething() or the unique symbol PENDING.
Now to my question. In addition to setTimeout, there's a setImmediate function that basically behaves like a timer that expires immediately; it just gives the event loop a go before resolving. When using this in my Promise.race expression above, empirically it seems to work, i.e., if p is already resolved it will beat setImmediate in a race, but I'm wondering if there is any such guarantee - or if I should be using a timer of 1 ms to guarantee that a resolved promise beats the timer?
I've tried putting p before and after the setImmediate promise in the array passed to Promise.race and it worked both ways, but still I'm worried it might behave randomly or be OS dependent? Or is the fact that setImmediate waits for one round of I/O enough to guarantee that any resolved promises will win?
From the documentation:
Schedules the "immediate" execution of the callback after I/O events' callbacks
Edit:
I found that even this "seems" to work:
const result = await Promise.race([p, new Promise(resolve => resolve(PENDING))]);
or actually even this:
const result = await Promise.race([p, Promise.resolve(PENDING)]);
However, here the order is important. If p is resolved and is before the timeout promise, it will win, but if it's after the timeout promise in the array it will lose.
The question is the same though: is this approach guaranteed to let p win if it's already resolved?
Considering the codes below in Nodejs
Ref. https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/
setImmediate(() => console.log("setImmediate"));
setTimeout(() => console.log("setTimeout"));
process.nextTick(() => console.log("nextTick"));
Promise.resolve().then(() => console.log("Promise"));
console.log("sync");
Output:
You can notice the sequence and that's how it is executed in order.
sync
nextTick
Promise
setTimeout
setImmediate
To answer your question, we can wrap the codes in Promises like below:
(async function main() {
const result = await Promise.race([
new Promise(resolve => resolve("sync")),
new Promise(resolve => setImmediate(() => resolve("setImmediate"))),
new Promise(resolve => setTimeout(() => resolve("setTimeout"))),
new Promise(resolve => Promise.resolve().then(() => resolve("Promise"))),
new Promise(resolve => process.nextTick(() => resolve("nextTick"))),
]);
console.log({ result });
})();
As sync function is to be executed first, so it would be returned.
Output:
{ result: 'sync' }
You can comment one of the Promise above to see which one resolve first.
Related
I'd like to understand better under what conditions nodejs stops a running process. My guess was that it stops the process when both the stack and the event loop are empty. But the following program only prints hello once, whereas I was expecting it to loop forever, printing hello every second.
(async () => {
while (true) {
await new Promise(resolve => setTimeout(() => console.log("hello"), 1000))
}
})();
How does the while (true) loop interact with the event loop?
You haven't mistaken how NodeJS works. Your code just has a bug: resolve never gets called.
If you change it to the following, "hello" prints forever at 1 second intervals:
(async () => {
while (true) {
await new Promise(resolve => setTimeout(() => {
console.log("hello")
resolve();
}, 1000))
}
})();
The reason your code would still end is because, in NodeJS, the resolve function falls out of scope, indicating to the V8 JS engine that the Promise can never resolve. Therefore it ends the async () => {...}, which in turn quits since it's the last function still running.
You need to call your resolve() method ,such that loop can proceed further,like below
(async() => {
while (true) {
const data = await new Promise(resolve => setTimeout(() => resolve("hello"), 1000))
console.log(data)
}
})();
You haven't resolved your promise.
Node can tell that there are no more event sources that might make something interesting happen (no more timers are scheduled) so it exits, even though there is an unresolved promise.
Compare this version, which will print hello forever, because the promise is resolved when the timeout completes and a new timeout is scheduled, thus ensuring that there is always something in the event queue, giving Node.js a reason to carry on running your program.
(async () => {
while (true) {
await new Promise(resolve => setTimeout(() => {
console.log("hello");
resolve();
}, 1000))
}
})();
If resolve is not called, then the promise is not resolved, and the await never completes and no new timeout is ever scheduled. The event queue empties and node decides that to go on doing nothing would be futile, so it exits.
I would like to know, if awaiting a resolved promise, will lead to a synchronous code execution or may lead to asynchronous one.
I made this little snippet to check on browsers :
const promise = new Promise((resolve) => {
console.log('exec promise');
setTimeout(() => {
console.log('executed promise');
resolve();
}, 1000);
});
(async () => {
console.log('start');
for (let i = 0; i < 1e8; i += 1) {
await promise;
}
console.log('end');
})();
And it looks like browsers make it synchronous (considering the screen freeze).
BUT ... is it due to browser specific implementation ? Or by design ?
Here's an alternative demo that doesn't freeze the browser (which I think is getting the way of what we're trying to show), and which shows the opposite behaviour to your initial conclusion:
const p = new Promise((r) => r());
p.then(() => {
(async() => {
console.log('a');
await p;
console.log('p');
})()
console.log('b');
});
In this case, we get hold of p, a resolved promise. We then kick off an async function that awaits it. The output is:
a
b
p
IE the async function still returned control back (and b was logged) when it hit the await on the already resolved p. Only after b was logged and the event loop was free to return execution back to after the await was p subsequently logged.
Is this standard behaviour? Yes.
The specification effectively turns the awaited expression to the then part of a promise, and in steps 9 and 10 of a subsequent step makes the decision on how to proceed.
If promise.[[PromiseState]] is pending, then
a. Append fulfillReaction as the last element of the List that is promise.[[PromiseFulfillReactions]].
b. Append rejectReaction as the last element of the List that is promise.[PromiseRejectReactions]].
Else if promise.[[PromiseState]] is fulfilled,
a. then Let value be promise.[[PromiseResult]].
b. Let fulfillJob be NewPromiseReactionJob(fulfillReaction, value)
c. Perform HostEnqueuePromiseJob(fulfillJob.[[Job]], fulfillJob.[[Realm]]).
Step 9 says if it's pending, to add the generated then to the list of things to be called when the promise is fulfilled.
Step 10 gets to the heart of your question - it says if it's fulfilled already, to enqueue the job - ie to place it on the queue of things to be called back.
The spec effectively says that an await should never return synchronously.
This is expected behavior. You freeze the event loop there, because the promise has already been resolved, the next await operator will wait&resolve for the next promise descriptor on the same iteration of the event loop. But this behavior can be platform-specific, depending on which promise implementation you're actually using - native or shim.
const promise= Promise.resolve();
(async()=>{
for(let i=0; i< 10000000; i++) {
await promise;
}
console.log('end');
})()
setTimeout(()=> console.log('nextEventLoopTick'), 0);
Will output the follwing:
end
nextEventLoopTick
As you can see, the async fn will be fully resolved before setTimeout. So, this indicates that the async function is resolved on the same eventloop tick.
Program - 1
new Promise(resolve => {
resolve('1');
Promise.resolve().then(() => console.log('2'));
}).then(data => {
console.log(data);
}); // output 2 1
Program -2
new Promise(resolve => {
Promise.resolve().then(() => {
resolve('1');
Promise.resolve().then(() => console.log('2'));
});
}).then(data => {
console.log(data);
}); // output 1 2
I am really confused with the output of both the programs. Can anyone please tell me how the execution thread works here?
What is hard to understand is that resolve doesn't work the same way as return. Creating Promises actually creates a new async context. Everything inside the first function of the Promise is executed synchronously. Then the .then() methods are called with the values resolved synchronously or asynchronously.
In the first example, you resolve '1', but then you create a new Promise with Promise.resolve() and you call the .then() right away. Because the new Promise is inside the first one, everything is called synchronously.
new Promise(resolve => {
resolve('1'); // Will be accessed in the .then() after then end of the callback
Promise.resolve().then(() => log('2')); // called before the end of the function
// because the context is not yet created
}).then(data => {
log(data);
}); // output 2 1
The second example is way harder. You actually create a Promise inside the first Promise and call its resolve right away. The order of execution will be:
first everything in the initial callbacks
After that, create sort of eventListeners from the resolve to the .then
When a resolve is called, execute the callback in .then
There is nothing in the initial function of the nested promise. So it goes straight to the then. The .then() calls the initial resolve(). Here, the kind of eventListener is created so we jump to the initial callback. Then we go back and execute the rest of the function.
new Promise(resolve => {
// Because we call resolve right away, the context of the original promise
// is created
Promise.resolve().then(() => {
resolve('1'); // Calling resolve jumps us to the .then
Promise.resolve().then(() => log('2')); // We execute after.then
});
}).then(data => {
log(data);
}); // output 1 2
If you removed the .resolve(), it would work like the first one:
new Promise(resolve => {
new Promise(() => {
resolve('1');
Promise.resolve().then(() => log('2'));
});
}).then(data => {
log(data);
}); // output 2 1
The difference lies in the context that the event loop is in when resolving the promise (resolve(1)).
There are 2 queues that execute code in JS:
microtask Q
macrotask Q
JS executes a microtask and then runs all the tasks from the macrotask Q (if any)
When resolving a promise from the executor (Promise(...)) you are running inside the macrotask Q. Code executed from inside a promise callback is executed on the microtask Q.
The difference that matters in this case is that when running from inside the microtask Q if you add other microtask (promise callbacks are microtasks) they get added to the current Q and get processed during this queue run.
This is what happens in case no 2, you are resolving the promise from inside a microtask Q, this ends up resolving the top level Promise and add the .then(data => { log(data); }); to the current microtask Q. So the callback will get executed in this run. On the other hand, the handlers of the nested promise Promise.resolve() is not executed now, as handlers are always called async.
Note: it is possible to add microtasks ad infinitum from inside the microtask Q, blocking the execution.
I don’t understand why this piece of code results in such an order? Could anyone elaborate on this? I thought Promises were like a FIFO queue, but the nested Promise functions seems a little bit unpredictable, or maybe using some other data structure?
new Promise(resolve => {
resolve()
})
.then(() => {
new Promise(resolve => {
resolve()
})
.then(() => {
console.log(1)
})
.then(() => {
console.log(2)
})
.then(() => {
console.log(3.1)
})
})
.then(() => {
console.log(1.1)
new Promise((resolve => {
resolve()
}))
.then(() => {
new Promise(resolve => {
resolve()
})
.then(() => {
console.log(4)
})
.then(() => {
console.log(6)
})
}).then(() => {
console.log(5)
})
}).then(() => {
console.log(3)
})
console.log(0)
Output:
0
1
1.1
2
3
3.1
4
5
6
Promises are async. This means everytime you create a new promise- a new async operation starts.
What is async operation in JS? First you need to understand that JS operates on a single thread no matter what you do. So, to make it looks like its asynchronous- there is something called the "event loop" (took the link from comment to original post, tnx #Taki for the great source).
In general, the event loop stores all the async functions and "slips" in the actions between the main code actions. This is really over-simplified explanation, refer to the link to read more, but thats the gist of it.
So basically, there is no "FIFO" queue here- the async functions order is literally depends on stuff like your processor speed, your operating system, etc.
BUT- there is a way to make sure one async action does get performed only after another one finishes, and this is the .then clause. The thing is, it only assures the specific function inside the .then will be performed after the specific promise it was concatenated to, but it does not say anything about the order of it in regars to other async operations (promises) in the event loop. So for example in your code:
new Promise(resolve => {
resolve() // PROMISE A
})
.then(() => {
new Promise(resolve => {
resolve() // PROMISE B
})
.then(() => {
console.log(1) //PROMISE C
})
.then(() => {
console.log(2)
})
.then(() => {
console.log(3.1)
})
})
.then(() => {
console.log(1.1) // PROMISE D
new Promise((resolve => {
resolve()
}))
I took part of it to explain:
so, Promise A resolves first. this assures that promise B will resolve now. here is when things gets complicated: since promise B is resolved, both promise C and D now get into event loop! why? because Promise A had 2 .then clauses, so when the first one ends- event loop takes the 2nd one which is promise D. but the first .then clause had also a .then clause of his own - promise C, which also enters the event loop.
THERE IS NO CONNECTION BETWEEN PROMISE D AND C! They could be performed in any order. keep that logic and you'll see how it works out for the rest of the promises, and also if you try to run it on different OS it might be that promises order will be different because of different implementations of the OS for the event loop.
Hope this helps you to understand a little.
DISCLAIMER: I have not much experience in JS, but promises really intrigued me so I did a deep research about it. I'm standing behind everything I wrote here, but if there are any corrections to my explanation I'd love to hear!
EDIT
The answer beneath me is also correct but with no explanation, so let me add to it:
When you do not return anything inside a promise (or a .then clause, which also returns a promise), it will implicitly return a resolved promise with no value before going out of the promise, basically like adding a return new Promise.resolve() after teh console.log in promise C, for example. When its done like this, all the .then clauses coming after promise B will only enter the event loop after the previous one ended (e.g b ends, so C goes into loop, then the next .then and so on), but between them other promises or .then clauses (like promise D) can enter as well.
But, when you RETURN the promise that has the .then clauses chained to it- it makes sure the whole block of the promise + then clauses goes into event loop as one in order, so the .then clauses will also be performed in the order you wanted :)
tnx #Eugene Sunic for the addition!
It results in unpredictable order because of un-existing returns in your code.
Adding return to your promises and you'll get comprehensible outputs and can easily track the promises execution.
Firstly, synchronous 0 is printed then the entire first promise block gets executed, like you said FIFO.
1,2, 3.1
After that the chaining thenable gets executed
1.1
After that the block 4,6 gets printed
following the chaining thenable which outputs 5 and at last, the last thenable prints number 3
Leaving us with 0,1,2, 3.1, 1.1, 4,6,5,3
new Promise(resolve => resolve())
.then(() => {
return new Promise(resolve => resolve())
.then(() => console.log(1))
.then(() => console.log(2))
.then(() => console.log(3.1));
})
.then(() => {
console.log(1.1);
return new Promise((resolve => resolve()))
.then(() => {
return new Promise((resolve) => resolve())
.then(() => console.log(4))
.then(() => console.log(6))
}).then(() => console.log(5))
}).then(() => console.log(3))
console.log(0)
It's FIFO and the execution looks like this:
main [4] logs: 0 // main code executed, one executor added to FIFO (4)
4 [8,18] // executor at line 4 runs, two executors added to FIFO (8, 18)
8 [18,11] logs: 1 // etc etc
18 [11,23,36] logs: 1.1
11 [23,36,14] logs: 2
23 [36,14,27,33]
36 [14,27,33] logs: 3
14 [27,33] logs: 3.1
27 [33,30] logs: 4
33 [30] logs: 5
30 logs: 6
as you can see its first in first out order: [4,8,18,11,23,36,14,27,33,30] but it stores executors (callbacks for promises that were fulfilled or rejected), not promises. In other words: the time when promise is fulfilled or rejected decides when its added to FIFO not the time the promise is created.
I met an article about promises in js, where the author shows a piece of code:
// I want to remove() all docs
db.allDocs({include_docs: true}).then(function (result) {
result.rows.forEach(function (row) {
db.remove(row.doc);
});
}).then(function () {
// I naively believe all docs have been removed() now!
});
and says
What's the problem with this code? The problem is that the first
function is actually returning undefined, meaning that the second
function isn't waiting for db.remove() to be called on all the
documents. In fact, it isn't waiting on anything, and can execute when
any number of docs have been removed!
So, as I understood, if the second callback function() {} accepts no arguments, it actually doesn't wait for the end of the callback function(result). Am I inferring correctly? Because when I run the following piece of code, it gives me an opposite result:
var array = [];
for ( let i = 0; i < 1000; i++ )
array.push(i);
var promise = new Promise((resolve, reject) => {
setTimeout(() => resolve(), 1000);
});
promise
.then(() => array.forEach(item => console.log(item)))
.then(() => console.log("invoked earlier"));
I wait for "invoked earlier" to appear in the middle of printed numbers. But doesn't matter how large the number of items is. "Invoked earlier" appears always at the end. Can someone explain me what I am missing? Maybe the article is outdated and something has changed since then?
In order to guarantee this, you actually have to return promises from the previous promise.
Whatever you return from a promise will be passed into the next promise.
In your case, you're returning undefined.
If, instead, you returned a promise, then the promise would be resolved, and the second promise would run after that happened, and would be passed whatever value the promise resolved with.
So yes, promises are guaranteed to run one after another, but if you choose to do something async inside of their callback, and don't bother chaining it back into the promise by returning it, then they're not going to bother to wait for anything (because they don't know that there's anything to wait for).
I'm assuming db.remove returns a promise...
...so, knowing that we have to return a promise from the callback, in order for it to actually wait for async stuff to happen, I would do something like this.
.then(result => Promise.all(result.rows.map(row => db.remove(row.doc))))
.then((listOfRemoves) => console.log(`removed`));
Whether the second function takes 1 argument or 0 arguments is 100% inconsequential as to when the second function runs.
Edit
examples:
.then(() => setTimeout(() => console.log('2nd', 20000)))
.then(() => console.log('first'));
This happens because the first then has NO idea that there is a setTimeout happening. It's not a mind-reader, it just runs the code and passes the return value along (in this case, undefined).
If the return value happens to be a promise, though, it will wait until that promise is done, get the value from it, and pass that on.
.then(() => new Promise(resolve => setTimeout(resolve, 20000)))
.then(() => console.log('I waited 20 seconds.'));
Because it's returning a promise, it will call the then of that promise, and wait to get the value, to pass on.
The reason your tests are failing is because you're basically doing this.
.then(() => console.log('first'))
.then(() => console.log('second'));
These are guaranteed to fire in this order. Period.
All of the other ones have been firing in that order as well. It's just that they are also scheduling async processes, just like all other callbacks/timeouts that use async I/O in JS.