Promise constructor with reject call vs throwing error
Is there any difference between using
reject
(inp2
) from thePromise
api, and throwing an error (inp1
) usingthrow
?
Yes, you cannot use throw
asynchronously, while reject
is a callback. For example, some timeout:
new Promise(_, reject) {
setTimeout(reject, 1000);
});
Its exactly the same?
No, at least not when other code follows your statement. throw
immediately completes the resolver function, while calling reject
continues execution normally - after having "marked" the promise as rejected.
Also, engines might provide different exception debugging information if you throw
error objects.
For your specific example, you are right that p1
and p2
are indistinguishable from the outside.
JavaScript Promises - reject vs. throw
There is no advantage of using one vs the other, but, there is a specific case where throw
won't work. However, those cases can be fixed.
Any time you are inside of a promise callback, you can use throw
. However, if you're in any other asynchronous callback, you must use reject
.
For example, this won't trigger the catch:
new Promise(function() {
setTimeout(function() {
throw 'or nah';
// return Promise.reject('or nah'); also won't work
}, 1000);
}).catch(function(e) {
console.log(e); // doesn't happen
});
Different error handling in Promise.resolve vs new Promise constructor
Promise Constructor Behaviour
The syntax for calling the promise constructor is
Promise( executorFunction)
which results in Promise
synchronously calling the executor function with two function arguments before returning the constructed promise. Per usual call the arguments resolve
and reject
for discussion purposes.
To clean up edge cases, Promise
returns a rejected promise if the executor throws before calling one of its arguments, with the rejection reason of the returned promise set to the thrown error. On the other hand, if the executor calls either resolve
or reject
synchronously and goes on to throw an error afterwards, the returned promise will be resolved or rejected according to the argument function called: the thrown error will be ignored!
Case 1
Promise.resolve(someError)
.catch(() => {
console.error('This never gets printed');
})
Before calling the Promise.resolve
method, the JavaScript Engine evaluates the argument to pass to the method. If evaluating someError
errors with a syntax or run time error, code execution stops due to the error encountered. "Everything stops", Promise.resolve
is not called and no following code gets executed either.
Case 2
new Promise ((resolve, reject) => {
resolve(someError)
})
.catch(() => {
console.error('This actually gets printed');
})
Here someError
is evaluated inside the executor as the argument to pass to resolve
. Since it errors the JavaScript engine never calls resolve
. But the defined behavior for Promise
for the case of the executor throwing early is to return a rejected promise with reason set to the thrown error. The .catch
clause in later invoked and prints 'This actually gets printed';
Standard (permalink)
If the executor throws, the ECMA Script 2015 ("ES6") standard requires the constructor to call the reject
function of the returned promise in step 10 of section 25.4.3.1. However, this won't affect a promise's state if it has been synchronously resolved or rejected already: additional calls to resolve
/reject
functions are silently ignored if one of the pair has been called previously.
Dealing with errors in promises that are unanticipated and thus don't trigger reject()
Remove the async
from the new Promise( async function( ...))
and it works as expected. That async
is catching the exception and turning it into a rejected promise which the promise is not expecting. It is not part of the contract with the new Promise()
constructor that you pass it a function that returns a promise that it should pay attention to.
Instead, the Promise constructor catches synchronous exceptions in the executor function or waits for you to call the resolve()
or reject()
callbacks and pays zero attention to whatever is returned from the executor callback function.
You can see here that if you remove the async
keyword, then it works as expected and the exception is caught in the .catch()
.
function test() {
return new Promise(function( resolve, reject ) {
objectThatDoesntExist.property = 1;
} );
}
test().catch( (error) => { console.log("Caught " + error) } );
asynchronous function in promise throws error and doesn't reject
Errors that are thrown inside a Promise
constructor and that occur asynchronously need to be explicitly try
/catch
ed so that reject
can be called, so that the Promise control flow can be transferred to the Promise's catch
. For example:
// this promise will have an error since param is not defined, and the promise wont be catchedfunction randomPromise(param) { return new Promise((resolve, reject) => { setTimeout(() => { try { param[0] = 11; } catch(e) { reject(e); } }, 2000); });}
randomPromise() .catch((e) => { console.log(e.message); });
Why can't try/catch handle error throw in Promise constructor
It's not caught because you're passing an async
function into new Promise
. An error inside an async
function rejects the promise that the function returns. The Promise
constructor doesn't do anything a promise returned by the function you pass it (the return value of that function is completely ignored), so the rejection goes unhandled. This is one of the promise anti-patterns: Don't provide a promise to something that won't handle it (like addEventListener
on the web, or the Promise
constructor, or forEach
, ...).
Similarly, there's no reason to use new Promise
in your async
function at all. async
functions already return promises. That's another anti-pattern, sometimes called the explicit promise construction anti-pattern. (But see below if you're wrapping an old-fashioned callback API.)
If you remove the unnecessary new Promise
, it works as you expect (I also updated run
to call callback
rather than ignoring it and calling throwError
directly):
async function main() {
try {
await run(throwError);
} catch (error) {
console.log("main catch error", error);
}
}
async function run(callback) {
return await callback();
}
async function throwError() {
throw new Error("custom error");
}
process.on("unhandledRejection", (reason, promise) => {
console.log("unhandledRejection - reason", reason, promise);
});
main();
About the return await callback();
in that example: Because whatever you return from an async
function is used to settle the function's promise (fulfilling it if you return a non-thenable, resolving it to the thenable if you return one), and that return
is at the top level of the function (not inside a try
/catch
or similar), you could just write return callback();
. In fact, you could even remove async
from run
and do that. But keeping the await
makes the async stack trace clearer on some JavaScript engines, more clearly indicates what you're doing, and keeps working correctly even if someone comes along later and puts a try
/catch
around that code.
In a comment, you said that you used new Promise
because you were wrapping around a callback-based API. Here's how you'd do that (see also the answers here):
// Assuming `callback` is a Node.js-style callback API, not an
// `async` function as in the question:
function run(callback) {
return new Promise((resolve, reject) => {
callback((err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
}
but, you don't have to do that yourself if the callback-based API uses the standard Node.js convention as above, there's a promisify
function in utils
.
Error handling in the Promise constructor
when it comes to the Promise constructor, you have to manually handle the possible errors.
[…]
When the above code runs, the promise never gets resolved (or rejected)
You misobserved that. In any ES6-compliant Promise
implementation, the promise will get rejected when you throw
an exception (synchronously) from the new Promise
constructor's resolver callback. This throw
-safety was one of the compelling arguments to favor the Promise
constructor over the deferred pattern.
How to properly handle reject in Promises
Couple of problems in your code:
You are unnecessarily creating a promise;
auth.post(..)
already returns a promise, so you don't need to create a promise yourself and wrapauth.post(...)
inside a promise constructor.Another problem in your code is that executor function (function passed to the promise constructor) is marked as
async
; it should not be an async function.
Your function could be re-written as:
const userLogin = async (loginData) => {
const res = await auth.post("/login", loginData);
if (res.status !== 201) {
throw new Error("Error"));
}
return res;
};
You could also re-write your function as:
const userLogin = async (loginData) => {
return auth.post("/login", loginData);
};
Don't forget to use the catch
in the code that calls this function.
You might want to read the following article to understand whether you need the try-catch
block: await vs return vs return await
Extend Javascript promise and resolve or reject it inside constructor
The reasoning is simple but not necessarily self evident.
.then()
returns a promise- if
then
is called on a subclass of Promise, the returned promise is an instance of the subclass, not Promise itself. - the
then
returned promise is constructed by calling the subclass constructor, and passing it an internal executor function that records the value ofresolve
andreject
arguments passed to it for later use. - "later use" covers resolving or rejecting the promise returned by
then
asynchronously when monitoring execution ofonfulfilled
oronrejected
handlers (later) to see if they return a value (which resolves thethen
returned promise) or throw an error (which rejects the promise).
In short then
calls internally obtain and record references to the resolve
and reject
functions of promises they return.
So regarding the question,
new MyPromise( 'p1')
works fine and is the first call to the subclass constructor.
.then( someFunction)
records someFunction
in a list of then
calls made on new MyPromise
(recall then
can be called multiple times) and attempts to create a return promise by calling
new MyPromise( (resolve, reject) => ... /* store resolve reject references */
This is the second call to the subclass constructor coming from then
code. The constructor is expected to (and does) return synchronously.
On return from creating the promise to return, the .then
method makes an integrity check to see if the resolve
and reject
functions it needs for later use are in fact functions. They should have been stored (in a list) along with callbacks provided in the then
call.
In the case of MyPromise
they are not. The executor passed by then
, to MyPromise
, is not even called. So then
method code throws a type error "Promise resolve or reject function is not callable" - it has no means of resolving or rejecting the promise it is supposed to return.
When creating a subclass of Promise, the subclass constructor must take an executor function as its first argument, and call the executor with real resolve
and reject
functional arguments. This is internally required by then
method code.
Doing something intricate with MyPromise
, perhaps checking the first parameter to see if it is a function and calling it as an executor if it is, may be feasible but is outside the scope of this answer! For the code shown, writing a factory/library function may be simpler:
function namedDelay(name, delay=1000, value=1) {
var promise = new Promise( (resolve,reject) => {
setTimeout(() => {
resolve(value)
}, delay)
}
);
promise.name = name;
return promise;
}
namedDelay( 'p1')
.then(result => {
console.log('fulfilled, result: ', result)
})
.catch(err => {
console.error('err: ', err)
})
Related Topics
Why Is Bind Slower Than a Closure
How to Access Parent Window Object Using Jquery
When to Use the Double Not (!!) Operator in JavaScript
JavaScript - Get Array Element Fulfilling a Condition
Jest: Timer and Promise Don't Work Well. (Settimeout and Async Function)
How to Set Image Source with Base64
JSON Stringify Changes Time of Date Because of Utc
How to Read Get Data from a Url Using JavaScript
Getting How Many People Are in a Chat Room in Socket.Io
Get Position/Offset of Element Relative to a Parent Container
JavaScript to Check When the Browser Window Is Closed
Jqgrid Gridunload/ Griddestroy
Open Page in New Window Without Popup Blocking