如何为JavaScript异步函数调用附加上下文?
同步函数调用上下文
在JavaScript中,通过全局作用域中的栈可以轻松为同步函数调用关联上下文。
// Context management let contextStack = []; let context; const withContext = (ctx, func) => { contextStack.push(ctx); context = ctx; try { return func(); } finally { context = contextStack.pop(); } }; // Example const foo = (message) => { console.log(message); console.log(context); }; const bar = () => { withContext("calling from bar", () => foo("hello")); }; bar();
这种方式让我们无需到处传递上下文对象,也无需让所有用到的函数都依赖该上下文对象,就能编写上下文相关的代码。
这在JavaScript中之所以可行,是因为代码执行具有顺序性保证——这些同步函数会执行完毕后,其他代码才能修改全局状态。
生成器函数调用上下文
我们可以用生成器函数实现类似的功能。生成器函数让我们能在其恢复执行前获得控制权。这意味着即使执行暂停数秒(即函数未执行完毕就有其他代码运行),我们仍能确保其执行时关联着正确的上下文。
const iterWithContext = function* (ctx, generator) { // not a perfect implementation let iter = generator(); let reply; while (true) { const { done, value } = withContext(ctx, () => iter.next(reply)); if (done) { return; } reply = yield value; } };
问题:如何为异步函数调用附加上下文?
为异步函数的执行附加上下文也会非常实用。
const timeout = (ms) => new Promise(res => setTimeout(res, ms)); const foo = async () => { await timeout(1000); console.log(context); }; const bar = async () => { await asyncWithContext("calling from bar", foo); };
据我所知,目前无法拦截异步函数恢复执行前或暂停执行后的时机来提供上下文,这就是问题所在。
有没有办法实现这一需求?
我目前的最优方案是不使用异步函数,而是采用表现类似异步函数的生成器函数。但这并不实用,因为需要整个代码库都按这种方式编写。
背景/动机
这种上下文用法极具价值,因为上下文可在调用栈深处访问。如果库需要调用外部处理器,且该处理器回调库时库能拥有合适的上下文,这一点就尤为有用。例如,我推测React Hooks和Solid.js在底层广泛采用这种上下文方式。若不这么做,开发者就必须到处传递上下文对象,在回调库时使用它,这既繁琐又容易出错。上下文是一种基于调用栈位置,巧妙地“柯里化”或抽象掉函数调用中上下文对象的方式。无论这是否为最佳实践尚有争议,但我们都认同这是库作者选择的方案。我希望将上下文的使用扩展到异步函数,因为从执行流程来看,异步函数在概念上应与同步函数表现类似。
悬赏说明
我现在意识到之前被采纳的方案在浏览器中无法生效,因为浏览器未实现异步堆栈跟踪。我希望有其他可行的替代方案,因此发起新一轮悬赏。
内容的提问来源于stack exchange,提问作者David Callanan

