I am a bit new to modern JavaScript (ES8). What is a preferred way to yield asynchronously, i.e. continue script execution upon some future iterations of the event loop, using await
? I saw the following options:
async function yield1() {
await Promise.resolve();
console.log("done");
}
async function yield2() {
// setImmediate is non-standard, only Edge and Node have it
await new Promise(done => (setImmediate? setImmediate: setTimeout)(done));
console.log("done");
}
async function yield3() {
await new Promise(done => setTimeout(done));
console.log("done");
}
Should I chose one after another or they're all the same? Or maybe it depends on the environment (node, browser)?
Updated, was asked in the ments about what I'm trying to achieve. It is a simple observable object that asynchronously fires propertyChanged
event when its properties change. Here is a full example, and the "yielding" piece is inside firePropertyChanged
:
const EventEmitter = require('events');
class Model extends EventEmitter {
constructor(data) {
super();
this._data = data;
}
get data() {
return this._data;
}
set data(newValue) {
const oldValue = this._data;
if (oldValue !== newValue) {
this._data = newValue;
this.firePropertyChanged('data', newValue, oldValue);
}
}
async firePropertyChanged(property, newValue, oldValue) {
await Promise.resolve().then(() =>
super.emit('propertyChanged', { target: this, property, newValue, oldValue }));
console.log('all propertyChanged handlers have been called asynchronously');
}
}
async function waitForChange(obj) {
await new Promise(resolve =>
obj.once('propertyChanged', args =>
console.log(`propertyChanged: ${args.property}, ${args.oldValue} -> ${args.newValue}`)));
}
async function test() {
const obj = new Model("old");
var change = waitForChange(obj);
console.log(`before change: ${obj.data}`);
obj.data = "new";
console.log(`after change: ${obj.data}`);
await change;
}
test().catch(e => console.error(e));
If you run it with node, the expected output should be:
before change: old after change: new propertyChanged: data, old -> new all propertyChanged handlers have been called asynchronously
The order of this output matters, i.e., I don't want any event handlers for propertyChanged
to be invoked before the setter method for data
has returned to the caller.
I am a bit new to modern JavaScript (ES8). What is a preferred way to yield asynchronously, i.e. continue script execution upon some future iterations of the event loop, using await
? I saw the following options:
async function yield1() {
await Promise.resolve();
console.log("done");
}
async function yield2() {
// setImmediate is non-standard, only Edge and Node have it
await new Promise(done => (setImmediate? setImmediate: setTimeout)(done));
console.log("done");
}
async function yield3() {
await new Promise(done => setTimeout(done));
console.log("done");
}
Should I chose one after another or they're all the same? Or maybe it depends on the environment (node, browser)?
Updated, was asked in the ments about what I'm trying to achieve. It is a simple observable object that asynchronously fires propertyChanged
event when its properties change. Here is a full example, and the "yielding" piece is inside firePropertyChanged
:
const EventEmitter = require('events');
class Model extends EventEmitter {
constructor(data) {
super();
this._data = data;
}
get data() {
return this._data;
}
set data(newValue) {
const oldValue = this._data;
if (oldValue !== newValue) {
this._data = newValue;
this.firePropertyChanged('data', newValue, oldValue);
}
}
async firePropertyChanged(property, newValue, oldValue) {
await Promise.resolve().then(() =>
super.emit('propertyChanged', { target: this, property, newValue, oldValue }));
console.log('all propertyChanged handlers have been called asynchronously');
}
}
async function waitForChange(obj) {
await new Promise(resolve =>
obj.once('propertyChanged', args =>
console.log(`propertyChanged: ${args.property}, ${args.oldValue} -> ${args.newValue}`)));
}
async function test() {
const obj = new Model("old");
var change = waitForChange(obj);
console.log(`before change: ${obj.data}`);
obj.data = "new";
console.log(`after change: ${obj.data}`);
await change;
}
test().catch(e => console.error(e));
If you run it with node, the expected output should be:
before change: old after change: new propertyChanged: data, old -> new all propertyChanged handlers have been called asynchronously
The order of this output matters, i.e., I don't want any event handlers for propertyChanged
to be invoked before the setter method for data
has returned to the caller.
-
2
Are you aware that
setImmediate
is not standardised? You might want to discount that one because of that in itself. – James Thorpe Commented Nov 16, 2018 at 11:40 - @JamesThorpe, no I'm not, thanks for pointing out. It seems to be working across all modern browsers and Node though (IE11 is out of the question). – avo Commented Nov 16, 2018 at 11:42
- 2 Interesting - the MDN is listing it as mostly not implemented across the board – James Thorpe Commented Nov 16, 2018 at 11:43
- 1 rxjs is a reactive js library. you can create observables which will feel familiar to using generators in say, python. rxjs-dev.firebaseapp. – Joey Gough Commented Nov 16, 2018 at 11:44
-
@JamesThorpe, you're right! Only Edge and Node have
setImmediate
. That leaves me with only #1 and #3. – avo Commented Nov 16, 2018 at 11:52
1 Answer
Reset to default 12OK, I'll address the new summary of your question in your ments (you should probably edit your question to just say this):
I want to run a piece of code on a future iteration of the event loop in the most efficient way (and let the current method return). No particular preferences, but but the order of continuations should matter. E.g., in my example, if property1 changed, then property2 changed, I first want propertyChanged be fired for property1, then for property2 (in both cases, asynchronously to the code that changed both properties).
The short version is you can use pretty much any of the options below to solve your issue. Without knowing more about your specific situations/requirements, I would probably remend setImmediate()
because it can't starve the event queue if triggered recursively, but either process.nextTick()
or Promise.resolve().then()
will trigger sooner (before other types of events) if that matters to your caller.
Here's some explanation of each choice - each will likely fulfill your objective, but each differs in some details.
All of these options allow the current tick of the event loop to finish and then they schedule a callback to be called on a future tick of the event loop. They differ in exactly when the next callback will be called and some will vary when they schedule the next callback based upon what type of event is currently be processed (e.g. where the event loop is in it's process of scanning several different event queues).
You can start by reading this overview article The Node.js Event Loop, Timers, and process.nextTick()
process.nextTick(cb)
This is the soonest way to schedule the callback. The current tick of the event loop finishes its execution and then before the node.js event loop code looks at any other event queues in the event loop, it looks for items in the nextTickQueue
and runs them. Note, it is possible to "starve" the event loop if you are continually calling process.nextTick()
recursively because it does not give other events a chance to run until the nextTickQueue
is empty. This is not a "fair" scheduler.
setImmediate(cb)
This schedules a callback to be run after the current "phase" of the event loop is finished. You can think of the event loop as cycling through a number of different types of queues. When the current type of queue that is being processed is empty, then any pending setImmediate()
callbacks will get processed.
Note, how this relates to other types of events, then depends upon what type of event was processing when setImmediate()
was called.
As an example, if you were in the pletion callback from fs.read()
and you called setImmediate()
to schedule a callback, then the event loop would first process any other pending I/O events before processing your setImmediate()
callback. Because it doesn't get called until the event loop advances to the next type of event in the event queue, you can't starve the event loop with setImmediate()
. Even recursively call setImmediate()
will still cycle through all events.
How a pending setTimeout()
is processed relative to a setImmediate()
that you schedule depends upon what phase of the event loop you were in when you called the setImmediate()
. This is generally beyond the scope of what you should be aware of in your code. If relative timing of multiple async operations like this is important, then you are much safer to just write code that guarantees a given sequence regardless of exactly when they operation is enabled by its callback. Promises can help you sequence things like this.
setTimeout(cb, 0)
Timers are one phase of the event loop. As it goes around the event loop looking at different types of event queues, one of the stages is to look for any timer events whose time has passed and thus it is time to call their callback. Because of this timers only run when the event loop is in the "timer phase" so how they fire relative to other types of events is indeterminate. It depends upon where the event loop is in its cycle when the timer is ready to go. Personally, I generally don't use setTimeout(cb, 0)
unless I'm trying to synchronize with other timer events as this will guarantee a FIFO order with other timer events, but not with other types of events.
Promise.resolve().then(cb)
To get to this level of detail for promises (which you normally don't need to), you have to be very aware of what the promise implementation you are using is and how it works. A non-native-code promise implementation will use one of the other timing mechanisms to schedule its .then()
handlers. Any of them can appropriately meet the Promise specification so they can vary.
Native promises in node.js do have a specific implementation. Personally, I know of no reason why you should write code that depends upon this specific implementation, but lots of people seem to be curious so I'll explain.
You can see a good diagram in this article: Promises, nextTicks and setImmediates. Native promises are implemented using what's called a micro tasks queue. It's essentially another queue like the nextTick queue that is processed after the nextTickQueue
, but before any of the other queues. So, queued up .then()
or .catch()
handlers run immediately after and nextTick
calls that are already scheduled and before any other types of events (timers, I/O pletion, etc...).
Non-native promise implementations (like Bluebird or Q) don't have the ability to make a new microTasks queue that is processed after the nextTick queue so they use setImmediate()
or process.nextTick()
.