Struggling with Promise.race() implementation - javascript

I saw following implementation of Promise.race().
I am finding it difficult of understand, how it's working.
const race = function(promisesArray) {
return new Promise((resolve, reject) => {
promisesArray.forEach((innerPromise) => {
Promise.resolve(innerPromise)
.then(resolve, reject)
.catch(reject);
});
});
};
1st part: Is the below statement true?
Promise.resolve(innerPromise) will always return a resolved promise with innerPromise as value and as it always resolves, I will always end up in .then block.
2nd Part:
I read in the explanation that resolve and reject passed to .then block will be called on resolution of innerPromise. Why?, shouldn't it be as Promise.resolve(innerPromise) always resolve, always 1st callback of .then block should get called?
I think I am missing something very basic. I have tried to find the solution but not able to find an explanation that clears my doubts.

The purpose of Promise.resolve in that code is to allow elements of the array to not be promises. They could be arbitrary thenables, or they could just be regular values (which will indeed become fulfilled promises).
const existingValue = 5;
const existingPromise = Promise.reject(new Error("blah"));
const existingThenable = {
then() {
console.log("never going to resolve >:)");
}
};
Promise.race([existingValue, existingPromise, existingThenable]).then(
value => { console.log("got a value", value); },
error => { console.log("got an error", error); },
);
1st part: Is the below statement true?
Promise.resolve(innerPromise) will always return a resolved promise with innerPromise as value and as it always resolves, I will always end up in .then block.
If you try removing existingValue from the array passed to Promise.race above, you’ll see that Promise.resolve doesn’t necessarily return a resolved promise; specifically, when it’s passed a promise or other thenable, it returns a promise that settles the same way (although when passed the same type of promise, it actually satisfies that obligation by returning the same promise object). So no, it’s not true. I think that answers part 2, too.
Additionally, although you didn’t bring it up: I’m pretty sure the additional .catch(reject) is unreachable/useless, at least for the standard ES Promise implementation.

Promise.resolve(anotherPromise) will always assume the state of anotherPromise, so if it anotherPromise is rejected, then the one from Promise.resolve() will also be rejected. Or if anotherPromise is fulfilled, the Promise.resolve() one will also be fulfilled with its value.
const rejectedPromise = Promise.reject("boom");
Promise.resolve(rejectedPromise)
.then(result => console.log("success:", result))
.catch(error => console.log("failure:", error));
See the documentation on MDN for Promise.resolve().
As for why the code is using Promise.resolve() instead of directly
innerPromise
.then(resolve, reject)
.catch(reject);
Promise.resolve() is useful when the input is not necessarily a promise. It can convert a plain value to a promise or an arbitrary thenable (potentially another promise implementation) to a vanilla JavaScript promise thus allowing for uniform way of handling the result.
Perhaps this is defensive coding or just allows for calling race(asyncResult, 42). The intention is not clear.
The resolve or reject parameters of into the executor function are noop when repeatedly called. A promise can reach a single final state - calling resolve/reject after that has no effect. Thus from the whole array, the first promise which leaves the pending state will determine what the promise constructor will be resolved as.
const p1 = new Promise((resolve, reject) => {
Promise.resolve("p1 success")
.then(resolve, reject)
.catch(reject);
Promise.reject("p1 failure")
.then(resolve, reject)
.catch(reject);
});
const p2 = new Promise((resolve, reject) => {
Promise.reject("p2 failure")
.then(resolve, reject)
.catch(reject);
Promise.resolve("p2 success")
.then(resolve, reject)
.catch(reject);
});
const p3 = new Promise((resolve, reject) => {
Promise.resolve("p3 hello")
.then(resolve, reject)
.catch(reject);
Promise.resolve("p3 world")
.then(resolve, reject)
.catch(reject);
});
const p4 = new Promise((resolve, reject) => {
Promise.reject("p4 foo")
.then(resolve, reject)
.catch(reject);
Promise.reject("p4 bar")
.then(resolve, reject)
.catch(reject);
});
p1.then(console.log, console.error);
p2.then(console.log, console.error);
p3.then(console.log, console.error);
p4.then(console.log, console.error);
Therefore, by looping and attaching the same resolve and reject to all promises, race will only resolve with the same outcome of the first promise to resolve. This matches the JavaScript implementation of Promise.race():
Return value
A Promise that asynchronously settles with the eventual state of the first promise in the iterable to settle. In other words, it fulfills if the first promise to settle is fulfilled, and rejects if the first promise to settle is rejected. The returned promise remains pending forever if the iterable passed is empty. If the iterable passed is non-empty but contains no pending promises, the returned promise is still asynchronously (instead of synchronously) settled.
N.B. iterable is the input to Promise.race(). It matches promisesArray of race().
With all that said, the following construct seems entirely superfluous:
p
.then(resolve, reject)
.catch(reject);
The second parameter to .then() is the onRejected callback. So if p is rejected, the second argument to .then() would be used to handle that. The extra .catch() would handle errors coming from either resolve or reject in the .then()
Promise.resolve("foo")
.then(
result => { throw `Fulfilled with ${result}. Throwing after success.` },
error => {throw `Fulfilled with ${error}. Throwing after error` }
)
.catch(errorFromThen => console.log(`Error in .catch() is: ${errorFromThen}`));
Promise.reject("bar")
.then(
result => { throw `Fulfilled with ${result}. Throwing after success.` },
error => {throw `Fulfilled with ${error}. Throwing after error` }
)
.catch(errorFromThen => console.log(`Error in .catch() is: ${errorFromThen}`));
Yet, neither resolve nor reject from the executor function can throw/reject in a plain Promise constructor.
//more verbose onError and .catch() handlers in order to showcase what gts shown or not
const p = new Promise((resolve, reject) => {
Promise.reject("hello")
.then(
resolve,
error => {
console.log("inside the onReject in .then()", error);
return reject(error);
})
.catch(error => {
console.log("inside the .catch()", error);
return reject(error);
});
Promise.reject("world")
.then(
resolve,
error => {
console.log("inside the onReject in .then()", error);
return reject(error);
}
)
.catch(error => {
console.log("inside the .catch()", error);
return reject(error);
});
});
p.then(console.log, console.error);
The extra .catch() is thus not used.
Overall, race() behaves like the vanilla JavaScript Promise.race(). The only major difference is that Promise.race() accepts any iterable, while race() only handles arrays.
const delay = (ms, value) =>
new Promise(resolve => setTimeout(resolve, ms, value));
const array = [delay(300, "a"), delay(100, "b"), delay(200, "c")];
const iterable = array.values();
Promise.race(iterable)
.then(result => console.log(`First to fulfil was ${result}`));
const race = function(promisesArray) {
return new Promise((resolve, reject) => {
promisesArray.forEach((innerPromise) => {
Promise.resolve(innerPromise)
.then(resolve, reject)
.catch(reject);
});
});
};
const delay = (ms, value) =>
new Promise(resolve => setTimeout(resolve, ms, value));
const array = [delay(300, "a"), delay(100, "b"), delay(200, "c")];
const iterable = array.values();
race(iterable)
.then(result => console.log(`First to fulfil was ${result}`))
.catch(console.error);

Related

Promise chain continues after rejection

I'm having trouble to properly catch an error/reject in a promise chain.
const p1 = () => {
return new Promise((resolve, reject) => {
console.log("P1");
resolve();
});
};
const p2 = () => {
return new Promise((resolve, reject) => {
console.log("P2");
reject();
});
};
const p3 = () => {
return new Promise((resolve, reject) => {
console.log("P3");
resolve();
});
};
p1().catch(() => {
console.log("Caught p1");
}).then(p2).catch(() => {
console.log("Caught p2");
}).then(p3).catch(() => {
console.log("Caught p3");
}).then(() => {
console.log("Final then");
});
When the promise is rejected, the following .then still gets executed. In my understanding, when in a promise chain an error/reject happened, the .then calls that follow it are not executed any more.
P1
P2
Caught p2
P3
Final then
The rejection gets caught correctly, but why is "P3" logged after the catch?
What am I doing wrong?
To clarify #evolutionxbox, this is my expected result:
Promise.resolve().then(() => {
console.log("resolve #1");
return Promise.reject();
}).then(() => {
console.log("resolve #2");
return Promise.resolve();
}).then(() => {
console.log("resolve #3");
return Promise.resolve();
}).then(() => {
console.log("Final end");
}).catch(() => {
console.log("Caught");
});
This code works exactly like it should. And I can't see a difference to my code, except that I declared the functions separately.
The code above stops no matter where the promise is rejected.
Here is a synchronous equivalent of your code:
const f1 = () => {
console.log("F1");
};
const f2 = () => {
console.log("F2");
throw new Error();
};
const f3 = () => {
console.log("F3");
};
try {
f1();
} catch {
console.log("Caught f1");
}
try {
f2();
} catch {
console.log("Caught f2");
}
try {
f3();
} catch {
console.log("Caught f3");
}
console.log("Final code");
As you can see, that gives a matching result. Hopefully, looking at the synchronous code you would not be surprised why. In a try..catch you are allowed to attempt recovery. The idea is that the catch will stop the error propagation and you can hopefully continue further. Or if you do want to stop, you still have to explicitly throw again, for example:
doCode();
try {
makeCoffee();
} catch(err) {
if (err instanceof IAmATeapotError) {
//attempt recovery
makeTea();
} else {
//unrecoverable - log and re-throw
console.error("Fatal coffee related issue encountered", err);
throw err;
}
}
doCode();
This is also the purpose Promise#catch() serves - so you can attempt recovery or at least act when there was a problem. The idea is that after the .catch() you might be able to continue:
const orderPizza = (topping) =>
new Promise((resolve, reject) => {
if (topping === "pepperoni")
reject(new Error("No pepperoni available"));
else
resolve(`${topping} pizza`);
});
const makeToast = () => "toast";
const eat = food => console.log(`eating some ${food}`);
async function main() {
await orderPizza("cheese")
.catch(makeToast)
.then(eat);
console.log("-----");
await orderPizza("pepperoni")
.catch(makeToast)
.then(eat);
}
main();
In order to reject the promise chain from a .catch() you need to do something similar as a normal catch and fail at the error recovery by inducing another error. You can throw or return a rejected promise to that effect.
This code works exactly like it should. And I can't see a difference to my code, except that I declared the functions separately.
The code above stops no matter where the promise is rejected.
The second piece of code you show fails entirely after a reject because there are no other .catch()-es that are successful. It is basically similar to this synchronous code:
try {
console.log("log #1");
throw new Error();
console.log("log #2");
console.log("log #3");
console.log("Final end");
} catch {
console.log("Caught");
}
Thus if you do not want to recover early, you can also skip the .catch() instead of inducing another error.
Try this.
const p1 = (arg) => {
// Promise returns data in the respected arguments
return new Promise((resolve, reject) => {
// Data to be accessed through first argument.
resolve(arg);
});
};
const p2 = (arg) => {
return new Promise((resolve, reject) => {
// Data to be accessed through second argument.
reject(arg);
});
}
p1('p1').then(resolve => {
console.log(resolve + ' is handled with the resolve argument. So it is accessed with .then()');
}) // Since reject isn't configured to pass any data we don't use .catch()
p2('p2').catch(reject => {
console.log(reject + ' is handled with the reject argument. So it is accessed with .catch()');
}) // Since resolve ins't configured to pass any data we don't use .then()
// You would normally configure a Promise to return a value on with resolve, and access it with .then() when it completes a task successfully.
// .catch() would then be chained on to the end of .then() to handle errors when a task cannot be completed.
// Here is an example.
const p3 = () => {
return new Promise((resolve, reject) => {
var condition = true;
if (condition === true) {
resolve('P3');
} else {
reject('Promise failed!');
}
});
};
p3('p3').then(resolve => {
console.log(resolve);
}).catch(reject => {
console.log(reject);
})
You do not do anything wrong.
In your code you call the first promise p1. Then you write p1.catch(...).then(...).then(...).then(...). This is a chain which means that you should call then 3 times, because you called resolve method in the p1 promise (all these thens depend on the first promise).
When the promise is rejected, the following .then still gets executed.
Yes. Just to be accurate: the then and catch method calls are all executed synchronously (in one go), and so all promises involved are created in one go. It's the callbacks passed to these methods that execute asynchronously, as the relevant promises resolve (fullfill or reject).
In my understanding, when in a promise chain an error/reject happened, the .then calls that follow it are not executed any more.
This is not the case. The promise that a catch returns can either fullfill or reject depending on what happens in the callback passed to it, and so the callbacks further down the chain will execute accordingly when that promise resolves.
The rejection gets caught correctly, but why is "P3" logged after the catch?
As in your case the catch callback returns undefined (it only performs a console.log), its promise fullfulls! By consequence, the chained then callback -- on that promise -- is executed... etc.
If you want to "stop"
If you want to keep the chain as it is, but wish to have a behaviour where a rejection leads to no further execution of then or catch callbacks, then don't resolve the associated promise:
const stop = new Promise(resolve => null);
const p1 = () => {
return new Promise((resolve, reject) => {
console.log("P1");
resolve();
});
};
const p2 = () => {
return new Promise((resolve, reject) => {
console.log("P2");
reject();
});
};
const p3 = () => {
return new Promise((resolve, reject) => {
console.log("P3");
resolve();
});
};
p1().catch(() => {
console.log("Caught p1");
return stop; // don't resolve
}).then(p2).catch(() => {
console.log("Caught p2");
return stop;
}).then(p3).catch(() => {
console.log("Caught p3");
return stop;
}).then(() => {
console.log("Final then");
});

Error propagation in chained Promise not working as expected

I'm learning chaining in JS Promises from this site. Based on the high level example, I've written following code to understand error propagation better.
var promise = new Promise((resolve, reject) => {
reject('Rejected!');
});
promise.then(()=>new Promise ((resolve, reject) => resolve('Done!')), () => console.log('Failure of first Promise'))
.then(() => console.log('Success of nested Promise'), () => console.log('Failure of nested Promise'));
console.log('This will be still printed first!');
Here when I'm rejecting the first promise, It is giving logging Failure of first Promise and then Success of nested Promise.
Now I wonder how It's going in the success callback of nested Promise? As explained in the above mentioned article, it's clear that even one promise is failed (rejected), the failure callback should be invoked.
What I'm missing here? Thanks.
The code you have written is like
var promise = new Promise((resolve, reject) => { reject('Rejected!'); });
promise
.then(()=>new Promise ((resolve, reject) => resolve('Done!')))
.catch(() => console.log('Failure of first Promise'))
.then(() => console.log('Success of nested Promise'))
.catch(() => console.log('Failure of nested Promise'));
console.log('This will be still printed first!');
since catch also returns a promise, the then chained with catch will also be triggered, if there is only one catch at end of all then, that catch will be triggered without any then.
you can do the following to overcome this issue,
promise
.then(()=>new Promise ((resolve, reject) => resolve('Done!')))
.then(() => console.log('Success of nested Promise'))
.catch(() => console.log('Failure of Promise'));
console.log('This will be still printed first!');
Second then callback catches the error from previous promise (catches as in try..catch). In this specific case (there's no chance that first then callbacks result in rejection) this is the same as:
promise // rejected promise
.then(()=>new Promise ((resolve, reject) => resolve('Done!'))) // skips rejected promise
.catch(() => console.log('Failure of first Promise')) // results in resolved promise
.then(() => console.log('Success of nested Promise')) // chains resolved promise
.catch(() => console.log('Failure of nested Promise')); // skips resolved promise
This is because
The catch() method returns a Promises
MDN source - Promise.prototype.catch()
"use strict";
new Promise((resolve, reject) => {
reject('Error');
}).catch(err => {
console.log(`Failed because of ${err}`);
}).then(() => {
console.log('This will be called since the promise returned by catch() is resolved');
});
This will log
Failed because of Error
This will be called since the promise returned by catch() is resolved

Promises chaining: Adding an error handler when creating a Promise vs adding to a variable with a promise

function getPromise() {
return new Promise((resolve, reject) => {
setTimeout(reject, 2000, new Error('fail'));
});
}
const promise1 = getPromise();
promise1.catch(() => {
// NOP
});
promise1
.then(() => console.log('then promise1'))
.catch(() => console.error('catch promise1'));
const promise2 = getPromise().catch(() => {
// NOP
});
promise2
.then(() => console.log('then promise2'))
.catch(() => console.error('catch promise2'));
Output
catch promise1
then promise2
Explanations
Here promise2 will be processed differently then promise1. While promise1 will be rejected with 'fail' error, promise2 will be resolved with undefined.
My environment
Ubuntu 14.04, Node.js 10.1.0
Question
I think this behavior is not obvious. Why does the code work this way?
Because you chain the second then not to the rejecting promise, but to the promise returned by catch, which will resolve to whatever you return from the catch handler.
Promise(promise1)
-❎-> catch
-✔-> then -❎- > catch
-❎-----------------^
Promise
-❎-> catch(promise2)
-✔-> then -❎-> catch
-❎-----------------^
-✔------------^
In the first case, the promise rejects, so it will enter the catch directly assigned to it, and it will skip the then, going directly to the chained catch. In the second case, the promise rejects and the catch will be executed, but then it seems to handle the error, so the chained then will be called. If you don't want that, the catch has to rethrow the error, then the then will be skipped and it also enters the chained catch.
Catch returns a new promise:
let orig = Promise.reject("fail")
let p = orig.catch(console.log)
console.log("is p the same as orig? ", p === orig)
p.then(() => console.log('p is a promise?', p instanceof Promise))
So when you call
const promise2 = getPromise().catch(() => { //..})
and assign the value to a promise2 that is a brand new promise returned from catch. In the first case you use the oringal promise1 in both statements.
To make the first statement work like the second, you would need to do something like:
function getPromise() {
return new Promise((resolve, reject) => {
setTimeout(reject, 2000, new Error('fail'));
});
}
let promise1 = getPromise();
// reassign the value of promise1
promise1 = promise1.catch(() => {
// NOP
});
promise1
.then(() => console.log('then promise1'))
.catch(() => console.error('catch promise1'));
Which is also the equivalent of just doing:
promise1
.catch(() => {/* noop */ })
.then(() => console.log('then promise1'))
.catch(() => console.error('catch promise1'));
Also, if you want to make sure the error is passed down the chain, you can always return a rejected promise from catch:
let promise1 = getPromise().catch((e) => {
return Promise.reject(e)
});

How to chain Promises and callback-style code

I'm confused on how this chaining for promises work, I'm still fairly new to promises and js in general so excuse me
line three, return user.findOne({email}).then((user) => {, i'm just confused about how returning this promise does anything since it returns a other promise inside the .then()
UserSchema.statics.findByCredentials = function(email, password){
user = this;
return user.findOne({email}).then((user) => {
if (!user){
return Promise.reject();
}
return new Promise((resolve, reject) => {
bcrypt.compare(password, user.password, (err, res) => {
if (res){
resolve(user);
}else{
reject()
}
});
});
});
}
the findByCredentials model method being used in an express app
app.post("/users/login", (req, res) => {
var body = _.pick(req.body, ["email", "password"]);
User.findByCredentials(body.email, body.password).then((user) => {
res.send(body)
}).catch((e) => {
res.send("!");
})
A simpler example I just created, this part
return plus(1).then((res) => {
return new Promise((resolve, reject) => {
is the problem i'm having trouble understanding
function plus(a) {
return new Promise((resolve, reject) => {
resolve(a + 1);
});
}
function test() {
return plus(1).then((res) => {
console.log(res);
return new Promise((resolve, reject) => {
resolve("Test");
});
});
}
test().then((res) => {
console.log(res);
});
As #Bergi said in the comment of your OP, the true power or Promises comes from returning them in the then of other Promises.
This allows you to chain Promises in a clean way.
To chain Promises all your operations in the chain must be Promises.
Your bcrypt.compare function uses callbacks to signal that it's done, so you need that function convert to a Promise.
This is easy to do. Just wrap the callback-style code in a Promise and resolve the result of the callback or reject if the callback is called with an err.
const comparePassword = (a, b) => {
return new Promise((resolve, reject) => {
bcrypt.compare(a, b, (err, result) => {
// Reject if there was an error
// - rejection is `return`-ed solely for stopping further execution
// of this callback. No other reason for it.
if (err) return reject(err)
// Resolve if not.
resolve(result)
})
})
}
... and then we can chain properly:
UserSchema.statics.findByCredentials = function(email, password) {
// Outer Promise:
// - Will eventually resolve with whatever the result it's inner
// promise resolves with.
return user.findOne({ email })
.then((user) => {
// Inner Promise:
// - Will eventually resolve with `user` (which is already
// available here), given that the password was correct,
// or
// reject with the bcrypt.compare `err` if the password was
// incorrect.
return comparePassword(password, user.password)
.then((result) => {
// This `then` belongs to the comparePassword Promise.
// - We use this so we can make sure we return the `user` we picked up
// from the previous `user.findOne` Promise.
// - This ensures that when you chain a `then` to this Promise chain
// you always get the `user` and not the result of `comparePassword`
return user
})
})
}
The key here is that whatever you return within a .then() is going to be passed as an argument to the next chained .then().
Additional info:
bcrypt.compare already returns a Promise, so we could have avoided the whole hassle of wrapping it into a Promise. I've intentionally used it with callbacks to illustrate how you should handle callback-style code in a Promise chain.

Why does .catch() not catch reject() within Promise constructor within loop at an async function unless Error is passed?

Given
(async () => {
const p = await new Promise((resolve, reject) => setTimeout(() => {
reject(new Error(1))
}, Math.floor(Math.random() * 1000))); return p})()
.then(data => console.log(data))
.catch(err => console.error(err));
the Error() is logged at .catch()
If we extend the pattern to use a loop the Error is logged at .catch()
const fn = async(res, ...props) => {
for (let prop of props) res.push(await prop())
return res
}
const arr = [
() =>
new Promise((resolve, reject) =>
setTimeout(() =>
reject(new Error(1))
, Math.floor(Math.random() * 1000))
),
() =>
new Promise((resolve, reject) =>
setTimeout(() =>
resolve(1)
, Math.floor(Math.random() * 1000))
)
];
fn([], ...arr)
.then(data => console.log(data))
.catch(err => console.log(err));
If we use a loop to call more than one function which returns a Promise and do not explicitly pass Error() to reject() of Promise constructor resolver function .catch() does not catch the error, and array res is not returned, only the Promise value passed to resolve() is available at
const fn = async(res, ...props) => {
for (let prop of props) res.push(await prop())
return res
}
const arr = [
() =>
new Promise((resolve, reject) =>
setTimeout(() =>
reject(1)
, Math.floor(Math.random() * 1000))
),
() =>
new Promise((resolve, reject) =>
setTimeout(() =>
resolve(1)
, Math.floor(Math.random() * 1000))
)
];
fn([], ...arr)
// what happened to our array `res` returned from `fn`?
.then(data => console.log(data))
// what happened to our rejected Promise?
.catch(err => console.log(err));
Questions:
Why does reject() call not propagate to .catch() when Error() is not explicitly passed to reject() within Promise constructor within a loop at an async function?
Why is only a single Promise value returned to .then() though an array is returned from the async function when one of the Promise objects iterated at a loop reject() function is called within Promise constructor resolver function?
If we […] do not explicitly pass Error() to reject() of Promise constructor resolver function .catch() does not catch the error
Sure, because there is no Error then. But catch will catch whatever you pass to reject, which is the value 1, which does get logged correctly.
When trying code, for example where Promise is used, and an error is expected to be logged at .catch(), explicitly pass Error(), preferably with a relevant message as parameter, to reject() of Promise constructor or Promise.reject(); and substitute using console.error() for console.log() at .catch(), for the purpose of distinguishing the error that you are expecting to be logged as an Error() and not a resolved Promise value, where the resolved and rejected value may happen to be the same.

Categories

Resources