Last active
May 15, 2024 08:08
-
-
Save dfkaye/270836e32a9c95c0ddffc4f0abab566e to your computer and use it in GitHub Desktop.
async/await dependency chains and what to do about them
This file contains 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
// 13 May 2024 | |
// async/await dependency chains | |
// 14 May 2024 | |
// added explicit try-catch-finally to test error in "finalizer" function C. | |
// what | |
// laughing at ben lesh tweet complaining about async/await being difficult | |
// without explaining why: | |
// https://twitter.com/BenLesh/status/1790115611277774913 | |
// why | |
// async/await dependency chains defeat the purpose of promises to begin with. | |
// how | |
// async functions return promises. promises were meant to ease the hell of | |
// nested event callbacks; however, if a promise is rejected for who knows what | |
// reason - especially an awaited promise - it must be caught, making any | |
// dependency between two or more such promises fraught with boilerplate, and | |
// suddenly we're back in callback hell. | |
// what ever shall we do? | |
// build a fake async chain to see how bad or how often the blocking and failing | |
// occur so we can then design for fallback values and behavior. | |
// our scenario mostly mimics ben's: | |
// if C depends on B which depends on A, and both B and A are hidden behind | |
// fetch requests, we can simulate the dependency of C on B and of B on A by | |
// faking non-determinism of rejecting or resolving each based on Date.now() | |
// being even, meanwhile preventing further requests if the fake click target | |
// is marked "busy". | |
// apply args to function r. | |
function Z(r, ...args) { | |
r.apply({}, args) | |
} | |
// return resolve if time is even, else return reject. | |
function Y(resolve, reject) { | |
return Date.now() % 2 == 0 | |
? resolve | |
: reject; | |
} | |
// fake fetch or other asynchronous process that returns a promise. | |
function X(v) { | |
return new Promise(function(resolve, reject) { | |
var r = Y(resolve, reject); | |
var P = async () => Z(r, v); | |
setTimeout(P, 1000 * Math.random()); | |
}); | |
} | |
// dependency | |
function A(v) { | |
var n = +v == v ? v + 19 : "0"; | |
return X(n).catch(_ => "A") | |
}; | |
// dependency | |
function B(v) { | |
var n = +v == v ? v * 105 : "999"; | |
return X(n).catch(_ => "B") | |
}; | |
// finalizer | |
async function C(...args) { | |
if (Date.now() % 2 == 0) { | |
throw Error(`C error: [${args}]`); | |
} | |
return { value: `[${args}]` }; | |
} | |
// fake click handler | |
async function onclick(e) { | |
var { busy, value } = e; | |
if (busy) { | |
console.warn("busy:", e.target, value, `\n\n`); | |
return | |
} | |
e.busy = true; | |
var a = await A(value); | |
var b = await B(a); | |
try { | |
var c = await C(a, b).catch(v => ({ error: `problem finalizing ${v}` })); | |
} | |
catch (err) { | |
console.assert(!err, "should not see this"); | |
} | |
finally { | |
'error' in c | |
? (console.error("finally", c.error, "for", value, `\n\n`)) | |
: (console.log("success", c.value, "for", value, `\n\n`)); | |
} | |
e.busy = false; | |
} | |
// fake event target | |
var e = {"target": "test" }; | |
/*** test it out ***/ | |
Array.from({ length: 10 }).forEach((v, i) => { | |
setTimeout(() => { e.value = i; onclick(e); }, i * 850); | |
}); | |
/* | |
busy: test 1 | |
finally problem finalizing Error: C error: [19,1995] for 0 | |
success [21,B] for 2 | |
busy: test 4 | |
finally problem finalizing Error: C error: [22,B] for 3 | |
success [A,B] for 5 | |
busy: test 7 | |
success [25,B] for 6 | |
busy: test 9 | |
success [A,B] for 8 | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment