Skip to content

Instantly share code, notes, and snippets.

@paulwib
Last active January 26, 2019 18:29
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save paulwib/6556c212ad8ba426cfc1effc650b4c8a to your computer and use it in GitHub Desktop.
Save paulwib/6556c212ad8ba426cfc1effc650b4c8a to your computer and use it in GitHub Desktop.
Chaining async functions
/**
* Chain an arbitrary number of async functions so they are executed in
* sequence until the final one resolves, or one of them rejects.
*
* @param {...Function} fns - multiple async functions
* @return {Function}
*/
function chain(...fns) {
if (fns.length === 0) throw new Error('chain: at least one argument required!');
const fn = fns.shift();
if (typeof fn !== 'function') throw new Error('chain: arguments must be functions');
if (fns.length === 0) return fn;
return (result) => fn().then(chain(...fns));
}
/**
* Utility to generate an async function which logs out when it's resolved.
*
* @param {Any} id - an identifier for the function
* @param {Number} delay - time to wait before resolving
*/
const asyncResolveFactory = (id, delay = 1) => () => {
// console.log('calling', id);
const msg = `resolve ${id}`;
return new Promise((resolve) => {
setTimeout(() => {
console.log(msg);
resolve(msg);
}, delay);
})
};
/**
* Untility to generate an async function which logs when it's rejected.
*
* @param {Any} id - an identifier for the function
* @param {Number} delay - time to wait before reject
*/
const asyncRejectFactory = (id, delay = 5) => () => {
// console.log('calling', id);
const msg = `reject ${id}`;
return new Promise((_, reject) => {
setTimeout(() => {
console.log(msg);
reject(msg);
}, delay);
})
};
// Chaining by hand is straightforward:
const async1 = asyncResolveFactory(1);
const async2 = asyncResolveFactory(2);
const async3 = asyncRejectFactory(3);
const async4 = asyncResolveFactory(4);
async1()
.then(async2)
.then(async3)
.then(async4)
.then((result) => {
console.log('result', result);
})
.catch((err) => {
console.log('result', err);
});
// But what if there an arbitrary number of async functions to execute in sequence?
// That's where chain() helps.
const async5 = asyncResolveFactory(5, 100);
const async6 = asyncResolveFactory(6, 100);
const async7 = asyncResolveFactory(7, 100);
const async8 = asyncResolveFactory(8, 100);
const chained = chain(async5, async6, async7, async8);
chained()
.then((result) => {
console.log('result', result);
})
.catch((err) => {
console.error('error', err);
});
// Because chains are just async functions, they can be combined into new chains!
let i = 0;
let a = [];
while (i++ < 5) a.push(asyncResolveFactory('chain a:' + i, 100));
i = 0;
let b = [];
while (i++ < 5) b.push(asyncResolveFactory('chain b:' + i, 200));
const chainA = chain(...a);
const chainB = chain(...b);
const chainCombined = chain(chainA, chainB);
setTimeout(async () => { // Timeout so doesn't get confused with previous output
const result = await chainCombined(); // Of course you can use await too!
console.log('result', result);
}, 700);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment