Reputation: 17574
I have a multitude of functions that may or may not return a Promise and that may or may not fail, like in the examples below:
let goodFun = (fruit) => `I like ${fruit}`;
let badFun = (fruit) => {
throw `${fruit} is spoiled!`;
};
let badPromise = (fruit) => new Promise( (fulfil, reject) => {
reject(`Promise failed with ${fruit}`);
});
let goodPromise = (fruit) => new Promise((fulfil, reject) => {
fulfil(`Promise succeeded with ${fruit}`);
});
Because each function may or may not return a promise, which may or may not fail, I modified executeSafe
from another StackOverflow post, that takes a function and its arguments as parameters, and then Promisifies the function and its result:
let executeSafe =
(fun, ...args) => Promise.resolve().then(() => {
return fun(...args);
});
My objective with all of this, is to have an asyncFun
function that waits for the execution of a batch of functions that were Promisified and then returns whatever came from executing them:
let asyncFun =
(fruit) =>
Promise.all([badFun, goodFun, badPromise, goodPromise].map(
fun => executeSafe(fun, fruit)
)
);
asyncFun
is designed to run the multitude of functions previously described and some of them I actually expect to see fail. To accommodate for this my asyncFun
function has a catch
clause. This clause only works with badFun
and doesn't work with badPromise
. The then
clause never works.
let executor = () => {
let fruitsArr = ["banana", "orange", "apple"];
let results = [];
for (let fruit of fruitsArr)
results.push(
asyncFun(fruit)
.then(res => {
console.log(res);
})
.catch(error => {
console.log(`Error: ${error}`);
})
);
return Promise.all(results);
};
Not even placing the catch
clause in the executor's call catches anything else other than the badFun
errors.
executor()
.catch(error => console.log("Failed miserably to catch error!"));
let goodFun = (fruit) => {
return `I like ${fruit}`;
};
let badFun = (fruit) => {
throw `${fruit} is spoiled!`;
};
let badPromise = (fruit) => Promise.resolve().then(() => {
throw `Promise failed with ${fruit}`;
});
let goodPromise = (fruit) => Promise.resolve().then(() => {
return `Promise succeded with ${fruit}`;
});
let executeSafe =
(fun, ...args) => Promise.resolve().then(() => {
return fun(...args);
});
let asyncFun = (fruit) => Promise.all([badFun, goodFun, badPromise, goodPromise].map(fun => executeSafe(fun, fruit)));
let executor = () => {
let fruitsArr = ["banana", "orange", "apple"];
let results = [];
for (let fruit of fruitsArr)
results.push(
asyncFun(fruit)
.then(res => {
console.log(res);
})
.catch(error => {
console.log(`Error: ${error}`);
})
);
return Promise.all(results);
};
executor()
.catch(error => console.log("Failed miserably to catch error!"));
Question:
then
and catch
clauses in asyncFun
work as intended?Upvotes: 0
Views: 1453
Reputation: 101652
The problem here is that you are misunderstanding how Promise.all()
works. The way it works is that if all of the promises succeed, it resolves to an array of values. If any of them fail, it rejects with the first error that occurred.
What you seem to be expecting, that the successful ones are caught in the then
and the failed ones are caught in the catch
, is not possible. A promise either resolves once or it rejects once. It doesn't do both, and it won't do one or the other multiple times. Promise.all()
returns a single promise so it will either resolve or reject.
Third party promise libraries do have methods for "settling" an array of promises - basically waiting until they have all done their thing (succeeded or failed), and resolving to an array of the results. You can implement it like this:
// values is an array of promises and/or non-promise values
function allSettled(values) {
let settle =
value => Promise.resolve(value)
.then(result => ({ state: "fulfilled", value: result }))
.catch(error => ({ state: "rejected", reason: error }));
return Promise.all(values.map(settle));
}
// example usage
allSettled(['hello', 'goodbye', Promise.resolve('good'), Promise.reject('bad')])
.then(results => console.log(results));
Then you can use it like below.
On an unrelated note, I've also modified your approach so that you don't need the modified version of executeSave
that takes ...args
(I think that's a convoluted way to go about it). You can create functions that use the arguments before you pass them into _executeSafe_
:
let goodFun = (fruit) => `I like ${fruit}`;
let badFun = (fruit) => {
throw `${fruit} is spoiled!`;
};
let badPromise = (fruit) => Promise.reject(`Promise failed with ${fruit}`);
let goodPromise = (fruit) => Promise.resolve(`Promise succeeded with ${fruit}`);
let executeSafe = fun => Promise.resolve().then(fun);
function allSettled(values) {
let settle =
value => Promise.resolve(value)
.then(result => ({ state: "fulfilled", value: result }))
.catch(error => ({ state: "rejected", reason: error }));
return Promise.all(values.map(settle));
}
let asyncFun =
(fruit) =>
allSettled([badFun, goodFun, badPromise, goodPromise]
.map(fun => () => fun(fruit))
.map(executeSafe)
);
asyncFun("orange").then(results => console.log(results));
Additional side note - if you wanted to use the promisify
function from Jared Smith's answer, then you could change your asyncFun
function to this:
let asyncFun =
(fruit) =>
allSettled([badFun, goodFun, badPromise, goodPromise]
.map(promisify)
.map(fun => fun(fruit))
);
Upvotes: 3
Reputation: 21926
So this works, catches fine. If the function passed to promisify
returns a promise, it just gets passed up the chain.
var fnThatThrows = s => { throw new Error(s) };
var identity = x => x;
var promisify = f => (...args) => new Promise((res, rej) => {
try {
res(f(...args));
} catch (e) {
rej(e);
}
});
var rejected = promisify(fnThatThrows);
rejected("foo").then(_ => console.log("shouldn't see")).catch(e => console.log(e)); // logs error
var succeded = promisify(identity);
succeeded("foo").then(val => console.log(val)); // logs 'foo'
If the promise from the argument function is rejected, that gets passed along too:
var returnsBadPromise = s => Promise.reject(new Error(s));
promisify(returnsBadPromise)("foo").catch(e => console.log(e)); // logs error
Upvotes: 0