Bluebird cancellation with await - javascript

This code is working correctly:
let promise;
try {
promise = parent();
//but I want: await parent();
await continueStack();
} catch (e) {
console.log('Caught error in endpoint');
}
eventBus.on('stop::all', () => {
console.warn('Caught stop::all event');
promise.cancel();
});
It's cancelling promise on event emitted from other part of program. My problem is that I can not have await parent() because then listening for event is not executed yet. If I change to this:
let promise;
eventBus.on('stop::all', () => {
console.warn('Caught stop::all event');
promise.cancel();
});
try {
promise = await parent();
await continueStack();
} catch (e) {
console.log('Caught error in endpoint');
}
then I have error
TypeError: Cannot read property 'cancel' of undefined
and it crash and continueStack() is never executed. How am I suppose to reconcile these?
I've created entire example with above scenerio:
https://gist.github.com/BorysTyminski/147dc2e0c44c8d64386253d563ff6db4
In order to run it you need to get both files, install package.json dependiecies and run cURL / Postman request GET to localhost:5000/test

You can do this by setting the promise var to parent, and then awaiting it:
const EventEmitter = require('events');
const Promise = require('bluebird');
Promise.config({
cancellation: true,
})
const parent = new Promise(resolve => {
setTimeout( () => {
resolve();
}, 100);
});
const continueStack = new Promise(resolve => {
setTimeout( () => {
resolve();
}, 100);
});
let promise;
const emitter = new EventEmitter();
emitter.on('stop', function() {
if(promise && promise.cancel){
promise.cancel();
console.log('promise cancelled');
} else {
console.log('somehow promise was not properly set at this point');
}
});
setTimeout(_ => {
emitter.emit('stop');
}, 80);
async function operation() {
try {
promise = parent;
await promise;
console.log('after promise. This log should never appear.');
await continueStack;
console.log('after wait 2. This log should never appear.');
} catch(e) {
console.log('Even if the promise is cancelled, this is not called.');
}
}
operation();
(running that snippet with node will only log 'promise cancelled'): Nothing after await promise inside the async function is executed).
Some considerations:
Promise.config is needed to set cancellable to true.
await is not like a sleep. It does not block the javascript thread nor the event listener, or any event emitter. It just ensures that code following an await will wait for the awaited promised. But this only in the context of an async function.
cancelling a Promise does not make the catch handler to fire. This puzzles me but I guess it is just how it works.

Related

How to catch exception in async context

I'm facing a problem of exception catching when the context is async.
Here is my minimal reproducible code
const { EventEmitter } = require('events');
const delaySync = function(ttd) {
return new Promise((resolve) => {
setTimeout(() => resolve(0), ttd);
});
};
const myEvent = new EventEmitter();
myEvent.on('something', async () => {
console.log('Event triggered');
// Just delay for 100ms and then throw
await delaySync(100);
throw new Error('Something happened');
});
describe('Events', () => {
it('should catch error', async () => {
await expect(myEvent.emit('something')).rejects.toThrow('Something happened');
});
});
Basically, I have an event emitter. It has a callback attached on event 'something' which is async. There are some async jobs inside and after 100ms delay it throws an error.
Normally I'd catch such errors in jest using await expect(fn).rejects.toThrow(...) however this case is different because expect fn expects a promise and myEvent.emit('something') is regular non-promise context.
In this case, I'm getting an error
● Events › should catch error
expect(received).rejects.toThrow()
Matcher error: received value must be a promise or a function returning a promise
Received has type: boolean
Received has value: true
...
(node:20242) UnhandledPromiseRejectionWarning: Error: Something happened
I've tried it with try-catch block as well but it did not work (not able to catch at all);
try {
myEvent.emit('something');
await delaySync(250);
} catch (e) {
expect(e.message).toBe('Something happened');
}
I've tried
// Not throwing because exception is not happening right away
expect(() => myEvent.emit('something')).toThrow('Something happened');
Also this
expect(async () => {
// Will thow after 100ms
myEvent.emit('something');
await delaySync(250);
}).toThrow('Something happened');
But no luck with any of them. 😕
Any ideas on how can I catch the error in this case? or any workaround?
The Eventemitter is fully sync and there is no workaround for it. What you can do is to use the eventemitter2 module to replace the original Eventemitter class. It is fully compatible with Eventemitter and allows you to use async actions.
var EventEmitter = require('eventemitter2');
const delaySync = function (ttd) {
return new Promise((resolve) => {
setTimeout(() => resolve(0), ttd);
});
};
const myEvent = new EventEmitter();
myEvent.on('something', async () => {
console.log('Event triggered');
// Just delay for 100ms and then throw
await delaySync(100);
throw new Error('Something happened');
// for async emitters pass promisify true.
}, { promisify: true });
describe('Events', () => {
it('should catch error', async () => {
await expect(myEvent.emit('something')).rejects.toThrow('Something happened');
});
}

Unexpected unhandledRejection event for promise which rejection does get handled

Updated, I've now tried explaining the behavior I'm seeing, but it'd still be great to have an answer from a credible source about the unhandledRejection behavor. I've also started a discussion thread on Reddit.
Why do I get an unhandledRejection event (for "error f1") in the following code? That's unexpected, because I handle both rejections in the finally section of main.
I'm seeing the same behavior in Node (v14.13.1) and Chrome (v86.0.4240.75):
window.addEventListener("unhandledrejection", event => {
console.warn(`unhandledRejection: ${event.reason.message}`);
});
function delay(ms) {
return new Promise(r => setTimeout(r, ms));
}
async function f1() {
await delay(100);
throw new Error("error f1");
}
async function f2() {
await delay(200);
throw new Error("error f2");
}
async function main() {
// start all at once
const [p1, p2] = [f1(), f2()];
try {
await p2;
// do something after p2 is settled
await p1;
// do something after p1 is settled
}
finally {
await p1.catch(e => console.warn(`caught on p1: ${e.message}`));
await p2.catch(e => console.warn(`caught on p2: ${e.message}`));
}
}
main().catch(e => console.warn(`caught on main: ${e.message}`));
Ok, answering to myself. I misunderstood how unhandledrejection event actually works.
I'm coming from .NET where a failed Task object can remain unobserved until it gets garbage-collected. Only then UnobservedTaskException will be fired, if the task is still unobserved.
Things are different for JavaScript promises. A rejected Promise that does not have a rejection handler already attached (via then, catch, await or Promise.all/race/allSettle/any), needs one as early as possible, otherwise unhandledrejection event may be fired.
When unhandledrejection will be fired exactly, if ever? This seems to be really implementation-specific. The W3C specs on "Unhandled promise rejections" do not strictly specify when the user agent is to notify about rejected promises.
To stay safe, I'd attach the handler synchronously, before the current function relinquishes the execution control to the caller (by something like return, throw, await, yield).
For example, the following doesn't fire unhandledrejection, because the await continuation handler is attached to p1 synchronously, right after the p1 promise gets created in already rejected state. That makes sense:
window.addEventListener("unhandledrejection", event => {
console.warn(`unhandledRejection: ${event.reason.message}`);
});
async function main() {
const p1 = Promise.reject(new Error("Rejected!"));
await p1;
}
main().catch(e => console.warn(`caught on main: ${e.message}`));
The following still does not fire unhandledrejection, even though we attach the await handler to p1 asynchronously. I could only speculate, this might be happening because the continuation for the resolved promised is posted as a microtask:
window.addEventListener("unhandledrejection", event => {
console.warn(`unhandledRejection: ${event.reason.message}`);
});
async function main() {
const p1 = Promise.reject(new Error("Rejected!"));
await Promise.resolve(r => queueMicrotask(r));
// or we could just do: await Promise.resolve();
await p1;
}
main().catch(e => console.warn(`caught on main: ${e.message}`));
Node.js (v14.14.0 at the time of posting this) is consistent with the browser behavior.
Now, the following does fire the unhandledrejection event. Again, I could speculate that's because the await continuation handler is now attached to p1 asynchronously and on some later iterations of the event loop, when the task (macrotask) queue is processed:
window.addEventListener("unhandledrejection", event => {
console.warn(`unhandledRejection: ${event.reason.message}`);
});
async function main() {
const p1 = Promise.reject(new Error("Rejected!"));
await new Promise(r => setTimeout(r, 0));
await p1;
}
main().catch(e => console.warn(`caught on main: ${e.message}`));
I personally find this whole behavior confusing. I like the .NET approach to observing Task results better. I can think of many cases when I'd really want to keep a reference to a promise and then await it and catch any errors on a later timeline to that of its resolution or rejection.
That said, there is an easy way to get the desired behavior for this example without causing unhandledrejection event:
window.addEventListener("unhandledrejection", event => {
console.warn(`unhandledRejection: ${event.reason.message}`);
});
async function main() {
const p1 = Promise.reject(new Error("Rejected!"));
p1.catch(console.debug); // observe but ignore the error here
try {
await new Promise(r => setTimeout(r, 0));
}
finally {
await p1; // throw the error here
}
}
main().catch(e => console.warn(`caught on main: ${e.message}`));
You should be using try...catch to catch all the errors happening inside your try block:
try {
await p2;
// do something after p2 is settled
await p1;
// do something after p1 is settled
}
catch(e) {
// do something with errors e
}
EDIT:
window.addEventListener("unhandledrejection", event => {
console.warn(`unhandledRejection: ${event.reason.message}`);
});
function delay(ms) {
return new Promise(r => setTimeout(r, ms));
}
async function f1() {
await delay(100);
throw new Error("error f1");
}
async function main() {
try {
const p1 = await f1();
await delay(200);
}
catch(e) {
console.warn(`caught inside main: ${e.message}`);
}
}
main().catch(e => console.warn(`caught on main: ${e.message}`));
I do not have a source but I think it works like this:
The Promise.reject(new Error("Rejected!")); returns a rejected promise that will error next tick.
so:
async function main3() {
//this wil throw the error next tick
const p1 = Promise.reject(new Error("Rejected!"));
//this will run immediately and attach the await to the promise (so it will not be rejected)
await p1;
}
Then Promise.resolve will return its result to all .then handler next tick (we don't have them as the wont store the result anywhere)
so:
async function main() {
//this wil throw the error next tick
const p1 = Promise.reject(new Error("Rejected!"));
//this will run immediately (and would give its value next tick)
await Promise.resolve();
//then this will run immediately and attach the await to the promise
await p1;
}
Lastly a setTimeout with 0 delay does not immediately trigger, check: https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop and read the 0 delay section
so:
async function main2() {
//this wil throw the error next tick
const p1 = Promise.reject(new Error("Rejected!"));
//setTimeout does with 0 does run not immediately.
//because of this the await p1 does not get added before the promise is rejected
await new Promise(r => setTimeout(r, 0));
//so this does nothing and the prosime will reject
await p1;
}

How to catch error in nested Promise when async/await is used [duplicate]

I'm using the async.eachLimit function to control the maximum number of operations at a time.
const { eachLimit } = require("async");
function myFunction() {
return new Promise(async (resolve, reject) => {
eachLimit((await getAsyncArray), 500, (item, callback) => {
// do other things that use native promises.
}, (error) => {
if (error) return reject(error);
// resolve here passing the next value.
});
});
}
As you can see, I can't declare the myFunction function as async because I don't have access to the value inside the second callback of the eachLimit function.
You're effectively using promises inside the promise constructor executor function, so this the Promise constructor anti-pattern.
Your code is a good example of the main risk: not propagating all errors safely. Read why there.
In addition, the use of async/await can make the same traps even more surprising. Compare:
let p = new Promise(resolve => {
""(); // TypeError
resolve();
});
(async () => {
await p;
})().catch(e => console.log("Caught: " + e)); // Catches it.
with a naive (wrong) async equivalent:
let p = new Promise(async resolve => {
""(); // TypeError
resolve();
});
(async () => {
await p;
})().catch(e => console.log("Caught: " + e)); // Doesn't catch it!
Look in your browser's web console for the last one.
The first one works because any immediate exception in a Promise constructor executor function conveniently rejects the newly constructed promise (but inside any .then you're on your own).
The second one doesn't work because any immediate exception in an async function rejects the implicit promise returned by the async function itself.
Since the return value of a promise constructor executor function is unused, that's bad news!
Your code
There's no reason you can't define myFunction as async:
async function myFunction() {
let array = await getAsyncArray();
return new Promise((resolve, reject) => {
eachLimit(array, 500, (item, callback) => {
// do other things that use native promises.
}, error => {
if (error) return reject(error);
// resolve here passing the next value.
});
});
}
Though why use outdated concurrency control libraries when you have await?
I agree with the answers given above and still, sometimes it's neater to have async inside your promise, especially if you want to chain several operations returning promises and avoid the then().then() hell. I would consider using something like this in that situation:
const operation1 = Promise.resolve(5)
const operation2 = Promise.resolve(15)
const publishResult = () => Promise.reject(`Can't publish`)
let p = new Promise((resolve, reject) => {
(async () => {
try {
const op1 = await operation1;
const op2 = await operation2;
if (op2 == null) {
throw new Error('Validation error');
}
const res = op1 + op2;
const result = await publishResult(res);
resolve(result)
} catch (err) {
reject(err)
}
})()
});
(async () => {
await p;
})().catch(e => console.log("Caught: " + e));
The function passed to Promise constructor is not async, so linters don't show errors.
All of the async functions can be called in sequential order using await.
Custom errors can be added to validate the results of async operations
The error is caught nicely eventually.
A drawback though is that you have to remember putting try/catch and attaching it to reject.
BELIEVING IN ANTI-PATTERNS IS AN ANTI-PATTERN
Throws within an async promise callback can easily be caught.
(async () => {
try {
await new Promise (async (FULFILL, BREAK) => {
try {
throw null;
}
catch (BALL) {
BREAK (BALL);
}
});
}
catch (BALL) {
console.log ("(A) BALL CAUGHT", BALL);
throw BALL;
}
}) ().
catch (BALL => {
console.log ("(B) BALL CAUGHT", BALL);
});
or even more simply,
(async () => {
await new Promise (async (FULFILL, BREAK) => {
try {
throw null;
}
catch (BALL) {
BREAK (BALL);
}
});
}) ().
catch (BALL => {
console.log ("(B) BALL CAUGHT", BALL);
});
I didn't realized it directly by reading the other answers, but what is important is to evaluate your async function to turn it into a Promise.
So if you define your async function using something like:
let f = async () => {
// ... You can use await, try/catch, throw syntax here (see answer of Vladyslav Zavalykhatko) ..
};
your turn it into a promise using:
let myPromise = f()
You can then manipulate is as a Promise, using for instance Promise.all([myPromise])...
Of course, you can turn it into a one liner using:
(async () => { code with await })()
static getPosts(){
return new Promise( (resolve, reject) =>{
try {
const res = axios.get(url);
const data = res.data;
resolve(
data.map(post => ({
...post,
createdAt: new Date(post.createdAt)
}))
)
} catch (err) {
reject(err);
}
})
}
remove await and async will solve this issue. because you have applied Promise object, that's enough.

promise then is not the function error for Node Promise

I am using async/await to return the Promise , to use it as promoise in node script. When I am trying to make use of the return value as Promise , its giving a error a.then is not a function
here is the sample code
function test () {
//do something .......
//....
return global.Promise;
}
(async ()=> {
let a = await test();
a.then(()=> { console.log('good ')}, (err)=> { console.log()});
})();
The Promise constructor function isn't a promise, it is a tool to make promises with.
Even if it was a promise, since you are awaiting the return value of test, it would have been resolved into a value before you try to call then on it. (The point of await is that it replaces the use of then() callbacks).
You can await a function that returns a promise like this:
function test() {
return new Promise((resolve, reject) => {
if (true) {
reject("Custom error message");
}
setTimeout(() => {
resolve(56)
}, 200);
})
}
async function main() {
try {
const a = await test();
console.log(a)
} catch (e) { // this handles the "reject"
console.log(e);
}
}
main();
If you change the true to false you can test the "resolve" case.
await retrieves the resolved value from a Promise
let a = await test(); // `a` is no longer a Promise
I've put together two ways of retrieving values from a Promise
using await
(async () => {
try {
let a = await test();
console.log('Good', a);
} catch(err) {
console.log(err);
}
})();
using .then()
test().then(a => {
console.log('Good', a);
}).catch(err => {
console.log(err);
});
Please note that, the async arrow function is removed because there is no await needed.

Is it an anti-pattern to use async/await inside of a new Promise() constructor?

I'm using the async.eachLimit function to control the maximum number of operations at a time.
const { eachLimit } = require("async");
function myFunction() {
return new Promise(async (resolve, reject) => {
eachLimit((await getAsyncArray), 500, (item, callback) => {
// do other things that use native promises.
}, (error) => {
if (error) return reject(error);
// resolve here passing the next value.
});
});
}
As you can see, I can't declare the myFunction function as async because I don't have access to the value inside the second callback of the eachLimit function.
You're effectively using promises inside the promise constructor executor function, so this the Promise constructor anti-pattern.
Your code is a good example of the main risk: not propagating all errors safely. Read why there.
In addition, the use of async/await can make the same traps even more surprising. Compare:
let p = new Promise(resolve => {
""(); // TypeError
resolve();
});
(async () => {
await p;
})().catch(e => console.log("Caught: " + e)); // Catches it.
with a naive (wrong) async equivalent:
let p = new Promise(async resolve => {
""(); // TypeError
resolve();
});
(async () => {
await p;
})().catch(e => console.log("Caught: " + e)); // Doesn't catch it!
Look in your browser's web console for the last one.
The first one works because any immediate exception in a Promise constructor executor function conveniently rejects the newly constructed promise (but inside any .then you're on your own).
The second one doesn't work because any immediate exception in an async function rejects the implicit promise returned by the async function itself.
Since the return value of a promise constructor executor function is unused, that's bad news!
Your code
There's no reason you can't define myFunction as async:
async function myFunction() {
let array = await getAsyncArray();
return new Promise((resolve, reject) => {
eachLimit(array, 500, (item, callback) => {
// do other things that use native promises.
}, error => {
if (error) return reject(error);
// resolve here passing the next value.
});
});
}
Though why use outdated concurrency control libraries when you have await?
I agree with the answers given above and still, sometimes it's neater to have async inside your promise, especially if you want to chain several operations returning promises and avoid the then().then() hell. I would consider using something like this in that situation:
const operation1 = Promise.resolve(5)
const operation2 = Promise.resolve(15)
const publishResult = () => Promise.reject(`Can't publish`)
let p = new Promise((resolve, reject) => {
(async () => {
try {
const op1 = await operation1;
const op2 = await operation2;
if (op2 == null) {
throw new Error('Validation error');
}
const res = op1 + op2;
const result = await publishResult(res);
resolve(result)
} catch (err) {
reject(err)
}
})()
});
(async () => {
await p;
})().catch(e => console.log("Caught: " + e));
The function passed to Promise constructor is not async, so linters don't show errors.
All of the async functions can be called in sequential order using await.
Custom errors can be added to validate the results of async operations
The error is caught nicely eventually.
A drawback though is that you have to remember putting try/catch and attaching it to reject.
BELIEVING IN ANTI-PATTERNS IS AN ANTI-PATTERN
Throws within an async promise callback can easily be caught.
(async () => {
try {
await new Promise (async (FULFILL, BREAK) => {
try {
throw null;
}
catch (BALL) {
BREAK (BALL);
}
});
}
catch (BALL) {
console.log ("(A) BALL CAUGHT", BALL);
throw BALL;
}
}) ().
catch (BALL => {
console.log ("(B) BALL CAUGHT", BALL);
});
or even more simply,
(async () => {
await new Promise (async (FULFILL, BREAK) => {
try {
throw null;
}
catch (BALL) {
BREAK (BALL);
}
});
}) ().
catch (BALL => {
console.log ("(B) BALL CAUGHT", BALL);
});
I didn't realized it directly by reading the other answers, but what is important is to evaluate your async function to turn it into a Promise.
So if you define your async function using something like:
let f = async () => {
// ... You can use await, try/catch, throw syntax here (see answer of Vladyslav Zavalykhatko) ..
};
your turn it into a promise using:
let myPromise = f()
You can then manipulate is as a Promise, using for instance Promise.all([myPromise])...
Of course, you can turn it into a one liner using:
(async () => { code with await })()
static getPosts(){
return new Promise( (resolve, reject) =>{
try {
const res = axios.get(url);
const data = res.data;
resolve(
data.map(post => ({
...post,
createdAt: new Date(post.createdAt)
}))
)
} catch (err) {
reject(err);
}
})
}
remove await and async will solve this issue. because you have applied Promise object, that's enough.

Categories