Why are two setTimeouts not in the same event loop - javascript

setTimeout(() => new Promise((r) => {console.log(1);r();}).then(() => console.log('1 mic')))
setTimeout(() => new Promise((r) => {console.log(2);r();}).then(() => console.log('2 mic')))
1
1 mic
2
2 mic
Why are two setTimeouts not in the same event loop
I didn't find an understandable answer after Google

As I stated in the Community Wiki answer, the simple reason is that calling setTimeout(fn) will, after the timeout expires, queue a new task to execute the callback.
So each call to setTimeout will queue its own task. [Specs]
But I have to note that your test wouldn't tell you if two callbacks are ran in the same event loop iteration anyway.
Indeed, every time* a callback is invoked, a microtask checkpoint is performed in the "cleanup after running a script" algorithm.
So even in a case where we'd have multiple callbacks firing in the same event loop iteration, we'd have a microtask checkpoint in between each callback. For instance, requestAnimationFrame() callbacks are all fired in the same event-loop iteration, as part of the "update the rendering" step of the event loop. But even there, microtasks will be fired:
requestAnimationFrame(() => {
console.log(1); Promise.resolve().then(() => console.log(1, "micro"));
// Try to include a new task in between
setTimeout(() => { console.log("a new task"); });
// block the event loop a bit so we ensure our timer should fire already
const t1 = performance.now();
while(performance.now() - t1 < 100) {}
});
requestAnimationFrame(() => {
console.log(2); Promise.resolve().then(() => console.log(2, "micro"));
});
So using microtasks is not a good way of checking if two callbacks are fired in the same event loop iteration. To do so, you could try to schedule a new task, like I did in that example. But even this isn't bullet proof, because browsers do have quite complex task prioritization system in place and we can't be sure when they'll fire different types of task.
The best, in supporting browsers, is to use the still under development Prioritized task scheduler.postTask method. This allows us to post tasks with the highest priority, and thus we can check if two callbacks are indeed in the same event loop iteration or not. In browsers that don't support this API we have to resort to using a MessageChannel object which should be the closest way of post high priority tasks:
const postTask = globalThis.scheduler
? (cb) => scheduler.postTask(cb, { priority: "user-blocking" })
: postMessageTask;
const test = (fn, label) => {
return new Promise((res) => {
let sameIteration = true;
fn(() => {
console.log(label, "first callback");
// If this task is executed before the next callback
// it means both callbacks weren't executed in the same iteration
postTask(() => sameIteration = false);
// block the event-loop a bit between both callbacks
const t1 = performance.now();
while(performance.now() - t1 < 100) {}
});
fn(() => {
console.log(label, "second callback");
console.log({ label, sameIteration });
res();
});
});
};
test(setTimeout, "setTimeout")
.then(() => test(requestAnimationFrame, "requestAnimationFrame") );
// If scheduler.postTask isn't available, use a MessageChannel to post high priority tasks
function postMessageTask(cb) {
const { port1, port2 } = (postMessageTask.channel ??= new MessageChannel());
port1.start();
port1.addEventListener("message", () => cb(), { once: true });
port2.postMessage("");
}
* That is, if the callstack is empty, which is the case most of the time, with one of the only exceptions being callbacks for events dispatched programmatically.

Because each timer callback has its own task scheduled.
[Specs]

Related

Loops in RxJS Observable don't stop after calling complete or error

When calling observer.complete() or observer.error() an observer stops sending data and is considered done. However, If there is a for loop in the observer, the loop will continue running even after observer.complete() is called. Can someone explain to me this behaviour? I expect that the loop would be cut short. The current behaviour means that an interval or a while loop will forever run in an Observable unless I unsubscribe in the code.
In the below snippet I added a console.log to see if the log will be called after observer.complete().
const testObservable = new Observable( observer => {
for (let count = 0; count < 11; count++){
observer.next(count);
if (count > 5) {
observer.complete()
console.log("test")
}
if (count > 7) {
observer.error(Error("This is an error"))
}
}}
);
let firstObservable = testObservable.subscribe(
next => {console.log(next)},
error => { alert(error.message)},
() => {console.log("complete")}
)
It is a responsibility of the create function to properly destroy resources. In your case there could be simply return statement after observer.complete().
But in general if needed, the create function should return the teardown function (TeardownLogic), that is called when the observable is finalised.
new Observable( observer => {
// observable logic, e.g. ajax request
return () => {
// teardown function, e.g. cancel ajax request
};
});
Observable just ignore future calls of next(...), complete() and error(...) once the observable is finalized. It means that complete() or error(...) function was called internally or the observable was unsubscribed externally.
new Observable( observer => {
observer.next('a');
observer.complete();
// next calls will be ignored
observer.next('b');
});
const observable = new Observable( observer => {
setTimeout(
() => observer.next('a'), // this will be ignored due to unsubscribe
10
);
});
const subscription = observable.subscribe(...);
subscription.unsubscribe();

Promise callback queue with priority for some callbacks

My code needs to perform a multitude of async actions simultaneously and handle their promises in a specific sequential way.
What I mean by "specific sequential way" ==> Assume you are starting promises promise1, promise2, promise3 in that order, and that promise3 actually resolves first, and promise2 second, then what I want is to process promise3, promise2 and promise1 sequentially in that order
Let's consider a timeout async function that times out after X seconds, and fetchMyItem1, fetchMyItem2, that return promises which, when fulfilled, should execute a different code depending whether timeout has resolved or not.
For a concrete scenario, imagine there is a customer waiting at the counter for an item to be delivered, and either the customer stays and we can serve him directly at the counter by bringing one item at a time, or the customer goes away (timeout) and we have to ask a waiter to come so that when the items arrive, he/she can bring the items to him. Note here that even when one item is being delivered, the other items should still be undergoing delivery (the promises are pending), and might even arrive (be fulfilled) while the customer is being served one item or the server arrives.
Here is some code to start with
const allItemsToBeDeliveredPromises = [fetchMyItem1(), fetchMyItem2(), ...]
const customerLeavesCounterPromise = timeout()
const waiter = undefined
const allPromisesToBeFulfilled = [...allItemsToBeDeliveredPromises, customerLeavesCounterPromise]
// LOOP
const itemDeliveredOrVisitorLeft = await Promise.all(allPromisesToBeFulfilled)
if hasCustomerLeft(itemDeliveredOrCustomerLeft) {
// hasCustomerLeft allows us to detect if the promise that resolved first is `customerLeavesCounterPromise` or not
waiter = await callWaiter()
} else {
// An item has arrived
if (waiter) {
deliverItemViaWaiter(itemDeliveredOrVisitorLeft)
} else {
deliverItemAtCounter(itemDeliveredOrVisitorLeft)
}
}
// remove itemDeliveredOrCustomerLeft from allPromisesToBeFulfilled
// END loop
I am not sure how to implement a loop for this scenario. Promises must be accumulated into a queue as they resolve, but there is a priority for a specific promise in the queue (the timeout promise should be executed asap when it arrives, but after finishing the processing of the current promise if a promise fulfilment is already being processed)
My code needs to perform a multitude of async actions simultaneously and handle their promises in a specific sequential way.
You could use streams to consume the promises, as streams are essentially queues that process one message at a time.
The idea (i.e. not tested):
import { Readable, Writable } from 'stream';
let customerHasLeft = false;
/*const customerLeavesCounterPromise = */timeout() // your code...
.then(() => { customerHasLeft = true; }); // ... made boolean
// let's push all our promises in a readable stream
// (they are supposedly sorted in the array)
const input = new Readable({
objectMode: true,
read: function () { // don't use arrow function: we need `this`
const allItemsToBeDeliveredPromises = [fetchMyItem1(), fetchMyItem2(), ...]; // your code
// put everything, in the same order, in the output queue
allItemsToBeDeliveredPromises.forEach(p => this.push(p));
this.push(null); // terminate the stream after all these
}
});
// let's declare the logic to process each promise
// (depending on `timeout()` being done)
const consumer = new Writable({
write: async function (promise, uselessEncoding, callback) {
try {
const order = await promise; // wait for the current promise to be completed
} catch (e) {
/* delivery error, do something cool like a $5 coupon */
return callback(e); // or return callback() without e if you don't want to crash the pipe
}
if (customerHasLeft) { /* find a waiter (you can still `await`) and deliver `order` */ }
else { /* deliver `order` at the counter */ }
callback(); // tell the underlying queue we can process the next promise now
}
});
// launch the whole pipe
input.pipe(consumer);
// you can add listeners on all events you'd like:
// 'error', 'close', 'data', whatever...
EDIT: actually we want to process promises as they resolve, but sequentially (i.e. a single post-process for all promises)
let customerHasLeft = false;
timeout() // your code...
.then(() => { customerHasLeft = true; }); // ... made boolean
const allItemsToBeDeliveredPromises = [fetchMyItem1(), fetchMyItem2(), ...];
const postProcessChain = Promise.resolve(); // start with a promise ready to be thened
// add a next step to each promise so that as soon as one resolves, it registers
// as a next step to the post-process chain
allItemsToBeDeliveredPromises.forEach(p => p.then(order => postProcessChain.then(async () => {
// do something potentially async with the resulting order, like this:
if (customerHasLeft) { /* find a waiter (you can still `await`) and deliver `order` */ }
else { /* deliver `order` at the counter */ }
})));
one of them must have the effect that, whenever it fulfils, it should alter the processing of other fulfilled promises that have not yet been processed or are not yet being processed.
Do you run them all together? Something like:
promise1 = asyncFunc1()
promise2 = asyncFunc2()
promise3 = asyncFunc3()
Promise.all([promise1, promise2, promise3]).then(//sometthing)
If yes, it can't be done, unless the promise2 function and promise3 function aren't waiting for an event, a lock, or something handled by the promise1 function.
If this is the case is better to organize the promises like:
asyncFunc1()
.then(() => asyncFunc2(paramIfOk))
.catch(() => asyncFunc2(paramIfFail))
In your example:
const allItemsToBeDelivered = [myPromise1(), myPromise2(), ...]
myPromise1() code should wait for the item to be delivered and check if someone is waiting for it. It's a model/code design problem, not a promise one.
Another way to do it is consider some event driven logic: an entity Customer has a listener for the delivered event that will be fired by the waitItemDelivered() promise just before resolve itself.
EDIT: as requested here a little more elaboration on the event-driven solution.
Short answer: it highly depend on your software design.
It's already something released and running in production? Be carefull with changes like this. If it's a service that you are developing you're still in time to take in consideration some logic changes. A solution that doesn't change radically how is working but use an events is not that great, mixing patterns never pay off on long term.
Example:
const events = require('events');
class Customer() {
constructor(shop) {
this.emitter = new events.EventEmitter()
this.shopEventListener = shop.emitter
this.setupEventLinstening() // for keeping things clean in the constructor
// other properties here
}
setupEventLinstening() {
this.shopEventListener.on('product-delivered', (eventData) => {
// some logic
})
}
buyProduct() {
// some logic
this.emitter.emit('waiting-delivery', eventData)
}
}
class Shop() {
constructor(shop) {
this.emitter = new events.EventEmitter()
// other properties here
}
addCustomer(customer) {
customer.emitter.on('waiting-delivery', (eventData) => {
// some logic
self.waitDelivery().then(() => self.emitter.emit('product-delivered'))
})
}
waitDelivery() {
return new Promise((resolve, reject) => {
// some logic
resolve()
})
}
}
// setup
const shop = new Shop()
const customer = new Customer(shop)
shop.addCustomer(customer)
This is a new way of seeing the logic, but a similar approach can be used inside a promise:
const waitDelivery = () => new Promise((resolve, reject) => {
logisticWaitDelivery().then(() => {
someEmitter.emit('item-delivered')
resolve()
})
}
const customerPromise = () => new Promise((resolve, reject) => {
someListener.on('item-delivered', () => {
resolve()
})
}
promiseAll([waitDelivery, customerPromise])
Make 2 queues, the second starting after the first ends:
var highPriority=[ph1,ph2,...]//promises that have to be executed first
var lowPriority=[pl1,pl2,...]//promises that have to wait the high priority promises
Promise.all(highPriority).then(()=>{
Promise.all(lowPriority)
})

Using RxJS how to buffer function calls until an other async function call has resolved

How can I use RxJS to buffer function calls until another async function has resolved?
Here is a simple example of what I'd like to accomplish
function asyncFunc(time) {
setTimeout(() => {
console.log('asyncFunc has resolved');
}, time);
}
function funcToBuffer(time) {
setTimeout(() => {
console.log(time);
}, time);
}
asyncFunc(3000);
funcToBuffer(1000);
funcToBuffer(2000);
funcToBuffer(4000);
funcToBuffer(5000);
asyncFunc(8000);
funcToBuffer(6000);
funcToBuffer(7000);
At the moment this code will print:
1000
2000
asyncFunc has resolved
4000
5000
6000
7000
asyncFunc has resolved
What I want is for this to print:
asyncFunc has resolved
1000
2000
4000
5000
asyncFunc has resolved
6000
7000
In essence, I want some kind of control flow that allows me to call funcToBuffer whenever I feel like, but under the hood, I want it to hold on executing whenever asyncFunc is executing and waiting to be resolved. Once asyncFunc has resolved, funcToBuffer calls should no longer be buffered and be executed right away.
I have tried playing with the buffer operator but I wasn't able to achieve the desired outcome.
If I understand it right, your main goal is to control the execution of a sequence of functions through a mechanism that buffers them until something happens, and that something is exactly what triggers the execution of the functions buffered.
If this is correct, the following could be the basis for a possible solution to your problem
const functions$ = new Subject<() => any>();
const buffer$ = new Subject<any>();
const executeBuffer$ = new Subject<any>();
const setBuffer = (executionDelay: number) => {
buffer$.next();
setTimeout(() => {
executeBuffer$.next();
}, executionDelay);
}
const functionBuffer$ = functions$
.pipe(
bufferWhen(() => buffer$),
);
zip(functionBuffer$, executeBuffer$)
.pipe(
tap(functionsAndExecuteSignal => functionsAndExecuteSignal[0].forEach(f => f()))
)
.subscribe();
Let me explain a bit the code.
First thing, we build functions$, i.e. an Observable of the functions we want to control. The Observable is built using a Subject, since we want to be able to control the notification of such Observable programmatically. In other words, rather than kicking the execution of a function like this funcToBuffer(1000), we create the function (as an object) and ask the functions$ Observable to emit the function like this
const aFunction = () => setTimeout(() => {console.log('I am a function that completes in 1 second');}, 1000);
functions$.next(aFunction);
In this way we have created a stream of functions that eventually will be executed.
Second thing, we create 2 more Observables, buffer$ and executeBuffer$, again using Subjects. Such Observables are used to signal when we have to create a buffer out of the functions emitted so far by functions$ and when we have to start the execution of the functions buffered.
These last 2 Observables are used in the function setBuffer. When you call setBuffer you basically say: please, create a buffer with all the functions which have been emitted so far by functions$ and start executing them after the executionDelay time specified as parameter.
The buffering part is performed by the functionBuffer$ Observable which is created using bufferWhen operator. The execution part is implemented leveraging the zip operator, that allows us to set the rhythm of execution of the functions based on the emissions of executeBuffer$ Observable.
You can test the above code setting up the following test data.
let f: () => any;
setBuffer(3000);
f = () => setTimeout(() => {console.log('f1');}, 1000);
functions$.next(f);
f = () => setTimeout(() => {console.log('f2');}, 2000);
functions$.next(f);
f = () => setTimeout(() => {console.log('f4');}, 4000);
functions$.next(f);
f = () => setTimeout(() => {console.log('f5');}, 5000);
functions$.next(f);
setBuffer(8000);
f = () => setTimeout(() => {console.log('f6');}, 6000);
functions$.next(f);
f = () => setTimeout(() => {console.log('f7');}, 7000);
functions$.next(f);
setBuffer(16000);
I started working on a solution with combineLatest but figured that a BehaviorSubject would be a better solution once I put more thought into it.
const { BehaviorSubject } = rxjs;
const { filter } = rxjs.operators;
let finalised$ = new BehaviorSubject(false);
function asyncFunc(time) {
setTimeout(() => {
console.log('asyncFunc has resolved');
if (!finalised$.getValue()) {
finalised$.next(true);
}
}, time);
}
function funcToBuffer(time) {
finalised$.pipe(filter(finalised => finalised)).subscribe(_ => { // Filter so only fire finalised being true
setTimeout(() => {
console.log(time);
}, time);
});
}
asyncFunc(3000);
funcToBuffer(1000);
funcToBuffer(2000);
funcToBuffer(4000);
funcToBuffer(5000);
asyncFunc(8000);
funcToBuffer(6000);
funcToBuffer(7000);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.2.2/rxjs.umd.min.js"></script>
CombineLatest that waits for both obsevables to fire.
const { of, combineLatest } = rxjs;
const { delay } = rxjs.operators;
let obs1$ = of(1).pipe(delay(1000));
let obs2$ = of(2).pipe(delay(2000));
let now = new Date();
combineLatest(obs1$, obs2$).subscribe(([obs1, obs2]) => {
let ellapsed = new Date().getTime() - now.getTime();
console.log(`${obs1} - ${obs2} took ${ellapsed}`);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.2.2/rxjs.umd.min.js"></script>

Detect end event in async system

I'm writing some backend stuff with RxJS. I need to raise some event when everything's done.
1) Architecture involve Subjects so onCompleted event never happens naturally. It may be emitted manually but to do it I need to depend on some END event which... loops the circle.
2) Architecture has pending$ Observable which keeps track of pending tasks.
Unfortunately, due to async nature of system, this state can be emptied several times so it's "emptiness" on itself can't be used as END indicator.
Here is my solution which is quite a hack because it requires explicit interval constant.
import {Observable, Subject} from "rx";
let pendingS = new Subject();
let pending$ = pendingS
.startWith(new Set())
.scan((memo, [action, op]) => {
if (action == "+") {
memo.add(op);
return memo;
} else if (action == "-") {
memo.delete(op);
return memo;
} else {
return memo;
}
}).share();
let pendingDone$ = pending$ // a guess that
.debounce(2000) // if nothing happens within 2 sec window
.filter(pending => !pending.size); // and nothing is pending => we're done
pendingDone$.subscribe(function () {
console.log("pendingDone!");
});
setTimeout(function () {
pendingS.onNext(["+", "op1"]);
pendingS.onNext(["+", "op2"]);
pendingS.onNext(["+", "op3"]);
pendingS.onNext(["-", "op2"]);
pendingS.onNext(["-", "op1"]);
pendingS.onNext(["-", "op3"]);
}, 100);
setTimeout(function () {
pendingS.onNext(["+", "op4"]);
pendingS.onNext(["-", "op4"]);
}, 500);
Is there more elegant solution to this (very general) problem?
So far I have found 3 approaches to the subj.
1) "Idle timeouts"
// next event
let pendingDone$ = pending$
.debounce(2000)
.filter(pending => !pending.size);
// next + completed events
let pendingDone$ = pending$
.filter(pending => !pending.size)
.timeout(2000, Observable.just("done"));
2) "Sync set, async unset"
If pending removal is suspended to the next event loop iteration, next pending tasks should come before that, removing "pending is empty" temporal condition.
pendingS.onNext(["+", "op"]);
// do something
setImmediate(() =>
pendingS.onNext(["-", "op"]);
);
pendingDone$
.filter(state => !state.size)
.skip(1); // skip initial empty state
3) Schedulers (?)
It's seemingly possible to achieve similar results with schedulers but that would probably be an unconventional usage so I'm not gonna dig into that direction.

What's the best way to unit test an event being emitted in Nodejs?

I'm writing a bunch of mocha tests and I'd like to test that particular events are emitted. Currently, I'm doing this:
it('should emit an some_event', function(done){
myObj.on('some_event',function(){
assert(true);
done();
});
});
However, if the event never emits, it crashes the test suite rather than failing that one test.
What's the best way to test this?
If you can guarantee that the event should fire within a certain amount of time, then simply set a timeout.
it('should emit an some_event', function(done){
this.timeout(1000); //timeout with an error if done() isn't called within one second
myObj.on('some_event',function(){
// perform any other assertions you want here
done();
});
// execute some code which should trigger 'some_event' on myObj
});
If you can't guarantee when the event will fire, then it might not be a good candidate for unit testing.
Edit Sept 30:
I see my answer is accepted as the right answer, but Bret Copeland's technique (see answer below) is simply better because it's faster for when the test is successful, which will be the case most times you run a test as part of a test suite.
Bret Copeland's technique is correct. You can also do it a bit differently:
it('should emit an some_event', function(done){
var eventFired = false
setTimeout(function () {
assert(eventFired, 'Event did not fire in 1000 ms.');
done();
}, 1000); //timeout with an error in one second
myObj.on('some_event',function(){
eventFired = true
});
// do something that should trigger the event
});
This can be made a little shorter with help of Sinon.js.
it('should emit an some_event', function(done){
var eventSpy = sinon.spy()
setTimeout(function () {
assert(eventSpy.called, 'Event did not fire in 1000ms.');
assert(eventSpy.calledOnce, 'Event fired more than once');
done();
}, 1000); //timeout with an error in one second
myObj.on('some_event',eventSpy);
// do something that should trigger the event
});
Here we're checking that not only has the event fired, but also if if event has fired only once during the time-out period.
Sinon also supports calledWith and calledOn, to check what arguments and function context was used.
Note that if you expect the event to be triggered synchronously with the operation that triggered the event (no async calls in between) then you can do with a timeout of zero. A timeout of 1000 ms is only necessary when you do async calls in between which take a long time to complete. Most likely not the case.
Actually, when the event is guaranteed to fire synchronously with the operation that caused it, you could simplify the code to
it('should emit an some_event', function() {
eventSpy = sinon.spy()
myObj.on('some_event',eventSpy);
// do something that should trigger the event
assert(eventSpy.called, 'Event did not fire.');
assert(eventSpy.calledOnce, 'Event fired more than once');
});
Otherwise, Bret Copeland's technique is always faster in the "success" case (hopefully the common case), since it's able to immediately call done if the event is triggered.
This method ensures the minimum time to wait but the maximum opportunity as set by the suite timeout and is quite clean.
it('should emit an some_event', function(done){
myObj.on('some_event', done);
});
Can also use it for CPS style functions...
it('should call back when done', function(done){
myAsyncFunction(options, done);
});
The idea can also be extended to check more details - such as arguments and this - by putting a wrapper arround done. For example, thanks to this answer I can do...
it('asynchronously emits finish after logging is complete', function(done){
const EE = require('events');
const testEmitter = new EE();
var cb = sinon.spy(completed);
process.nextTick(() => testEmitter.emit('finish'));
testEmitter.on('finish', cb.bind(null));
process.nextTick(() => testEmitter.emit('finish'));
function completed() {
if(cb.callCount < 2)
return;
expect(cb).to.have.been.calledTwice;
expect(cb).to.have.been.calledOn(null);
expect(cb).to.have.been.calledWithExactly();
done()
}
});
Just stick:
this.timeout(<time ms>);
at the top of your it statement:
it('should emit an some_event', function(done){
this.timeout(1000);
myObj.on('some_event',function(){
assert(true);
done();
});`enter code here`
});
Late to the party here, but I was facing exactly this problem and came up with another solution. Bret's accepted answer is a good one, but I found that it wreaked havoc when running my full mocha test suite, throwing the error done() called multiple times, which I ultimately gave up trying to troubleshoot. Meryl's answer set me on the path to my own solution, which also uses sinon, but does not require the use of a timeout. By simply stubbing the emit() method, you can test that it is called and verify its arguments. This assumes that your object inherits from Node's EventEmitter class. The name of the emit method may be different in your case.
var sinon = require('sinon');
// ...
describe("#someMethod", function(){
it("should emit `some_event`", function(done){
var myObj = new MyObj({/* some params */})
// This assumes your object inherits from Node's EventEmitter
// The name of your `emit` method may be different, eg `trigger`
var eventStub = sinon.stub(myObj, 'emit')
myObj.someMethod();
eventStub.calledWith("some_event").should.eql(true);
eventStub.restore();
done();
})
})
Better solution instead of sinon.timers is use of es6 - Promises:
//Simple EventEmitter
let emitEvent = ( eventType, callback ) => callback( eventType )
//Test case
it('Try to test ASYNC event emitter', () => {
let mySpy = sinon.spy() //callback
return expect( new Promise( resolve => {
//event happends in 300 ms
setTimeout( () => { emitEvent('Event is fired!', (...args) => resolve( mySpy(...args) )) }, 300 ) //call wrapped callback
} )
.then( () => mySpy.args )).to.eventually.be.deep.equal([['Event is fired!']]) //ok
})
As you can see, the key is to wrap calback with resolve: (... args) => resolve (mySpy (... args)).
Thus, PROMIS new Promise().then() is resolved ONLY after will be called callback.
But once callback was called, you can already test, what you expected of him.
The advantages:
we dont need to guess timeout to wait until event is fired (in case of many describes() and its()), not depending on perfomance of computer
and tests will be faster passing
I do it by wrapping the event in a Promise:
// this function is declared outside all my tests, as a helper
const waitForEvent = (asynFunc) => {
return new Promise((resolve, reject) => {
asyncFunc.on('completed', (result) => {
resolve(result);
}
asyncFunc.on('error', (err) => {
reject(err);
}
});
});
it('should do something', async function() {
this.timeout(10000); // in case of long running process
try {
const val = someAsyncFunc();
await waitForEvent(someAsyncFunc);
assert.ok(val)
} catch (e) {
throw e;
}
}
I suggest using once() for an even simpler solution, especially if you like async/await style:
const once = require('events').once
// OR import { once } from 'events'
it('should emit an some_event', async function() {
this.timeout(1000); //timeout with an error if await waits more than 1 sec
p = once(myObj, 'some_event')
// execute some code which should trigger 'some_event' on myObj
await p
});
If you need to check values:
[obj] = await p
assert.equal(obj.a, 'a')
Finally, if you're using typescript, the following helper might be handy:
// Wait for event and return first data item
async function onceTyped<T>(event: string): Promise<T> {
return <T>(await once(myObj, event))[0]
}
Use like this:
const p = onceTyped<SomeEvent>(myObj, 'some_event')
// execute some code which should trigger 'some_event' on myObj
const someEvent = await p // someEvent has type SomeEvent
assert.equal(someEvent.a, 'a')

Categories