Promise Constructor with Reject Call VS Throwing Error

Promise constructor with reject call vs throwing error

Is there any difference between using reject (in p2) from the Promise api, and throwing an error (in p1) using throw?

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/catched 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 wrap auth.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 of resolve and reject arguments passed to it for later use.
  • "later use" covers resolving or rejecting the promise returned by then asynchronously when monitoring execution of onfulfilled or onrejected handlers (later) to see if they return a value (which resolves the then 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



Leave a reply



Submit