Last active
January 26, 2019 18:29
-
-
Save paulwib/6556c212ad8ba426cfc1effc650b4c8a to your computer and use it in GitHub Desktop.
Chaining async functions
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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