I want to invoke an async method, followed by a series of async fallback methods until one of them succeeds.
If all invocations fail, then I want all of their errors printed. Otherwise, if even one succeeds, the errors should not be printed.
This is what I want:
tryX()
.catch(x => tryXFallback1()
.catch(xf1 => tryXFallback2()
.catch(xf2 => tryXFallback3()
.catch(xf3 => tryXFallback4()
// ...
.catch(xf4 => Promise.reject([x, xf1, xf2, xf3, xf4]))))));
But I'm not a fan of the indentation. Accumulating the errors in a variable outside the scope of the catch clauses also seems messy:
let errors = [];
tryX()
.catch(x => {
errors.push(x);
return tryXFallback1();
})
.catch(xf1 => {
errors.push(x);
return tryXFallback2();
})
.catch(xf2 => {
errors.push(x);
return tryXFallback3();
})
.catch(xf3 => {
errors.push(x);
return tryXFallback4();
})
// ...
.catch(xf4 => Promise.reject(errors));
Lastly, I thought I could do some sort of for loop instead but that seems even uglier e.g.:
let methods = [tryX, tryFallback1, tryFallback2, tryFallback3, tryFallback4, /*...*/];
let errors = [];
for (let x of methods)
try {
return await x();
} catch (e) {
errors.push(e);
}
if (errors.length === methods.length)
return Promise.reject(errors);
Does anyone know of a more elegant approach?
The loop you have seems fine. I would probably stick with it as it already works. However, here is an alternative:
function tryWithFallbacks(main, ...fallbacks) {
return fallbacks.reduce(
(p, nextFallback) => p.catch( //handle errors
err => nextFallback() //try using the fallback
.catch(e => Promise.reject(err.concat(e))) //propagate rejection reasons
//on failure by adding to
//the array of errors
),
main() //seed the process with the main task
.catch(err => Promise.reject([err])) //ensure there is an array of errors
);
}
const a = tryWithFallbacks(
() => Promise.resolve(42)
);
test(a, "a"); //42
const b = tryWithFallbacks(
() => Promise.reject("oops"),
() => Promise.resolve("it's fine")
);
test(b, "b"); //"it's fine"
const c = tryWithFallbacks(
() => Promise.reject("oops1"),
() => Promise.reject("oops2"),
() => Promise.reject("oops3")
);
test(c, "c"); //["oops1", "oops2", "oops3"]
const d = tryWithFallbacks(
() => Promise.reject("oops1"),
() => Promise.reject("oops2"),
() => Promise.reject("oops3"),
() => Promise.resolve("finally!")
);
test(d, "d"); //"finally!"
const e = tryWithFallbacks(
() => Promise.reject("oops1"),
() => Promise.reject("oops2"),
() => Promise.reject("oops3"),
() => Promise.resolve("penultimate try successful!"),
() => Promise.reject("this is not reached")
);
test(e, "e"); //"penultimate try successful!"
//a simple function to illustrate the result
function test(promise, name) {
promise
.then(result => console.log(`[${name}] completed:`, result))
.catch(errorResult => console.log(`[${name}] failed:`, errorResult));
}
It's Array#reduce()-ing all the promises into one and making sure of the sequential order. If any succeed, you just get a single successful result. On failure, the error response is added to an array of all errors and passed forward via the rejection flow of promises.
This currently does require that all the functions that produce a promise are thunks - they take no input.
For reference, an equivalent operation using await and a loop would be:
async function tryWithFallbacks(...tasks) {
const errors = [];
for (const task of tasks) {
try {
const result = await task();
return result;
} catch (err) {
errors.push(err);
}
}
return errors;
}
const a = tryWithFallbacks(
() => Promise.resolve(42)
);
test(a, "a"); //42
const b = tryWithFallbacks(
() => Promise.reject("oops"),
() => Promise.resolve("it's fine")
);
test(b, "b"); //"it's fine"
const c = tryWithFallbacks(
() => Promise.reject("oops1"),
() => Promise.reject("oops2"),
() => Promise.reject("oops3")
);
test(c, "c"); //["oops1", "oops2", "oops3"]
const d = tryWithFallbacks(
() => Promise.reject("oops1"),
() => Promise.reject("oops2"),
() => Promise.reject("oops3"),
() => Promise.resolve("finally!")
);
test(d, "d"); //"finally!"
const e = tryWithFallbacks(
() => Promise.reject("oops1"),
() => Promise.reject("oops2"),
() => Promise.reject("oops3"),
() => Promise.resolve("penultimate try successful!"),
() => Promise.reject("this is not reached")
);
test(e, "e"); //"penultimate try successful!"
//a simple function to illustrate the result
function test(promise, name) {
promise
.then(result => console.log(`[${name}] completed:`, result))
.catch(errorResult => console.log(`[${name}] failed:`, errorResult));
}
Use Promise.any():
Promise.any([tryXFallback1, tryXFallback2, tryXFallback3]).then(result => console.log(result)).catch(err => console.log(err.errors))
This solves everything you need:
Works if any async of the passed array is resolved
Logs all of the errors that were thrown (The object that is passed to the catch callback of any, has property .errors)
Related
Why does the following code print baz, done before 1, 2, 3?
const bar = () => Promise.resolve([1, 2, 3]);
const cat = e => {
console.log(e);
return Promise.resolve(e);
};
const foo = () =>
bar()
.then(arr => Promise.all(arr.map(e => cat(e))))
.then(console.log("baz"));
foo().then(console.log("done"));
You are executing console.log() immediately instead of passing it to callback function in .then(). This will do it:
const bar = () => Promise.resolve([1, 2, 3]);
const cat = e => {
console.log(e);
return Promise.resolve(e);
};
const foo = () =>
bar()
.then(arr => Promise.all(arr.map(e => cat(e))))
.then(() => console.log("baz"));
foo().then(() => console.log("done"));
You probably forgot to embed the console.log into arrow functions so their execution is properly deferred:
const bar = () => Promise.resolve([1, 2, 3]);
const cat = e => {
console.log(e);
return Promise.resolve(e);
};
const foo = () =>
bar()
.then(arr => Promise.all(arr.map(e => cat(e))))
.then(() => console.log("baz"));
foo().then(() => console.log("done"));
I want to create a mock of this object for unit testing purposes.
sqs.listQueues().promise()
.then(...
Here's one of my attempts at mocking this and still getting the error sqs.listQueues(...).promise is not a function
const sqs = {
listQueues: () => Promise.resolve(this),
promise: () => Promise.resolve()
}
How can i properly mock this object?
Looks like listQueues() should not return a promise. Maybe this would work:
const sqs = {
listQueues: () => ({
promise: () => Promise.resolve()
})
}
Here's a fairly naive version that might get you going.
const mockResolve = (val, delay = 0) => () =>
new Promise((res) => setTimeout(() => res(val), delay))
const mockReject = (err, delay = 0) => () =>
new Promise((_, rej) => setTimeout(() => rej(err), delay))
const sqs = {
listQueues: () => ({
promise: mockResolve('foo')
})
}
sqs.listQueues().promise().then(console.log)
There are probably many things wrong with this, but it's only meant as a first pass.
I have this bit of code:
const promises = new Array(20).fill(null).map(v => {
return c.lockp('foo').then((v => {
const rand = Math.random()*3000;
return new Promise((resolve) => setTimeout(resolve,rand)).then(_ => v);
})
.then(({key, id}) => c.unlockp(key, id)));
});
return Promise.all(promises)
.then(values => {
console.log('all good');
process.exit(0);
});
I am getting this error:
TypeError: (intermediate value)(intermediate value).then is not a
function
at Array.fill.map.v (/home/oleg/WebstormProjects/oresoftware/live-mutex/.r2g/tests/smoke-test.js:26:6)
at Array.map ()
at /home/oleg/WebstormProjects/oresoftware/live-mutex/.r2g/tests/smoke-test.js:20:43
It should be occurring on 5th line of code in the code snippet above.
Your .then is being called on the function with the v parameter (which is enclosed in the parentheses right before the .then). Put the .then outside instead, so that it gets called on the promise chain rather than on the callback:
const promises = new Array(20).fill(null).map(v => {
return c.lockp('foo')
.then(v => {
const rand = Math.random()*3000;
return new Promise((resolve) => setTimeout(resolve,rand)).then(_ => v);
})
.then(({key, id}) => c.unlockp(key, id));
I kind of got the same question as has been asked here.
I've made a function:
const rewardStayingViewersOrNewcomers = () => {
fetch('https://tmi.twitch.tv/group/user/instak/chatters')
.then(parseJSON)
.then(r => {
let chatters = r.chatters;
viewerKeys = Object.keys(chatters); // [mods, viewers,...]
let ChattersPerRole = viewerKeys.map(role => {
return chatters[role].map(username => ({
username, role
}));
});
return flattenDeep(ChattersPerRole);
}).catch(err => {
console.log(`Error in fetch: ${err}`);
});
};
Why can't I assign that return value to my variable? The log of that variable returns undefined...
let viewersPerRole = rewardStayingViewersOrNewcomers();
setTimeout(() => console.log(viewersPerRole), 7000);
Bonus question, how could I easily wait for viewersPerRole to be filled with the data I'm waiting for because of the fetch? (so I don't have to use setTimeout())?
First of all, try returning something from the main function. There's no return in front on the fetch(...). Code should look like:
const rewardStayingViewersOrNewcomers = () => {
return fetch('https://tmi.twitch.tv/group/user/instak/chatters')
.then(parseJSON)
.then(r => {
let chatters = r.chatters;
viewerKeys = Object.keys(chatters); // [mods, viewers,...]
let ChattersPerRole = viewerKeys.map(role => {
return chatters[role].map(username => ({
username, role
}));
});
return Promise.resolve(flattenDeep(ChattersPerRole));
}).catch(err => {
console.log(`Error in fetch: ${err}`);
});
};
// now that you're returning a promise
rewardStayingViewersOrNewcomers()
.then(viewersPerRole => console.log(viewersPerRole))
If you're using Babeljs to transpile with stage3 enabled, have a look at async / await.
I am reading mostly adequate guide to fp now. But I cannot figure out how to correctly compose this functions.
const R = require('ramda');
const {IO, Future} = require('ramda-fantasy');
const read = () => IO(() => 'hello');
const write = (data) => IO(() => {
console.log(data)
return data;
});
const process = (data) => Future((reject, resolve) => {
return setTimeout(() => resolve(data), 0);
});
What is the best way to combine functions: read -> process -> write ?
change the ios to futures.
const R = require('ramda');
const {Future} = require('ramda-fantasy');
const read = () => Future((reject, resolve) => resolve('hello'));
const write = (data) => Future((reject, resolve) => {
console.log(data)
resolve(data);
});
const process = (data) => Future((reject, resolve) => {
return setTimeout(() => resolve(data), 0);
});
read.chain(process)
.chain(write)
.fork(console.warn, console.log);
Futures are like async ios so theres not much point between lifting between the two types. If you really wanted to treat them differently you could nest the monad into IO> as bergi suggested.