I am being challenged trying to make an async call inside an event.
Here's the code from Nodemailer - I've added the line where I need to make an async call:
let transporter = nodemailer.createTransport({
SES: new aws.SES({
apiVersion: '2010-12-01'
}),
sendingRate: 1 // max 1 messages/second
});
// Push next messages to Nodemailer
transporter.on('idle', () => {
while (transporter.isIdle()) {
// I need to make an async db call to get the next email in queue
const mail = await getNextFromQueue()
transporter.sendMail(mail);
}
});
I found this post which suggest switching things around which makes sense however I have been unable to apply it correctly to this.
Update - The answer was to mock sendMail using Sinon.
emit("event1"); The neat thing about event emitters is that they are asynchronous by nature.
Yes, events are synchronous and blocking. They are implemented with simple function calls. If you look at the eventEmitter code, to send an event to all listeners, it literally just iterates through an array of listeners and calls each listener callback, one after the other.
Using async functions with event handlers is problematic, because it can lead to an unhandled rejection in case of a thrown exception: const ee = new EventEmitter(); ee. on('something', async (value) => { throw new Error('kaboom'); });
Async functions are available natively in Node and are denoted by the async keyword in their declaration. They always return a promise, even if you don't explicitly write them to do so. Also, the await keyword is only available inside async functions at the moment – it cannot be used in the global scope.
You can just mark your callback as async
and use await
inside of it.
The fact that it's an event
handler callback makes no difference since at the end it's just a plain-old Function
.
'use strict'
const EventEmitter = require('events')
const myEmitter = new EventEmitter()
const getDogs = () => {
return new Promise(resolve => {
setTimeout(() => {
resolve(['Woof', 'Woof', 'Woof'])
}, 500)
})
}
myEmitter.on('event', async () => {
const dogs = await getDogs()
console.log(dogs)
})
myEmitter.emit('event')
If you still can't get it to work it might be because transporter.on
is not the same as EventEmitter.on
- meaning it's a custom function provided by transporter
.
It could assume internally that the callback function provided is not a Promise
- keep in mind that labelling a function as async
forces the function to always implicitly return a Promise
.
If that's the case you might want to wrap the async
function in an IIFE.
// ..rest of code from above
myEmitter.on('event', () => {
// wrap into an IIFE to make sure that the callback
// itself is not transformed into a Promise
(async function() {
const dogs = await getDogs()
console.log(dogs)
})()
})
myEmitter.emit('event')
I had a similar scenario and if I were you I would have done the following.
let transporter = nodemailer.createTransport({
SES: new aws.SES({
apiVersion: '2010-12-01'
}),
sendingRate: 1 // max 1 messages/second
});
const sendMail = async () => {
while (transporter.isIdle()) {
// I need to make an async db call to get the next email in queue
const mail = await getNextFromQueue()
transporter.sendMail(mail);
}
}
// Push next messages to Nodemailer
transporter.on('idle', sendMail);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With