ECMAScript新提案:AsyncContext.Variable 和 AsyncContext.Snapshot

2023年 8月 30日 124.1k 0

JavaScript 已成为编程中最通用和使用最广泛的语言之一。无论您是在开发一个活泼的交互式网页,还是为您的 Web 应用程序制作一个强大、可扩展的后端,JavaScript 都有您需要的工具和库。但是,像所有语言一样,它当然有其局限性和挑战。其中一个挑战是处理异步操作,这是编程的一个重要方面。

异步编程使 JavaScript 能够以非阻塞的方式执行任务,这意味着它不必等待一个操作完成,然后再继续下一个操作。它是一个功能强大的工具,但也可能导致棘手的问题,尤其是在管理异步调用之间的上下文时。

这个问题是新的异步上下文提案可以进行解决。本文提供了一个全面的指南,用于理解异步上下文提案,它对服务器端JavaScript的影响,以及它对JavaScript开发的未来可能意味着什么。

如果你熟悉异步 JavaScript 的历史,我建议直接跳到什么是异步上下文 API?从这篇文章中获得最大收益。

了解异步 JavaScript 代码

在我们深入研究异步上下文提案之前,让我们确保我们对异步代码的含义达成共识。

简单来说,异步编程是一种设计模式,它允许程序在等待操作完成的同时继续执行其他任务。这种设计模式与同步编程形成鲜明对比,在同步编程中,程序按顺序执行每个任务,并且仅在当前任务完成后才继续执行下一个任务。

JavaScript是一种单线程语言,这意味着它一次只能做一件事。但是,它需要处理许多操作,如网络请求、计时器等,这些操作不能完全适应单线程任务队列。这就是事件循环的用武之地。

事件循环

事件循环是 JavaScript 环境中支持异步 JavaScript 的重要组成部分。它不断循环遍历调用堆栈并检查要执行的任务。

假设一个任务还没有准备好执行,例如当它正在等待来自服务器的数据时——事件循环可以移动到下一个任务,并在准备好后返回到等待任务。这样,JavaScript 可以随着时间的推移处理多个任务,而不会停止或阻止程序的其余部分。

JavaScript 提供了几种编写异步代码的技术。早期版本的 JavaScript 依赖于回调,其中一个函数将传递到另一个函数并在以后运行。然而,这种方法导致了臭名昭著的“回调地狱”,这是一种深度嵌套回调的情况,使代码难以阅读和维护。

JavaScript的Promise和 async/await

为了解决这个问题,JavaScript 引入了 Promise,它表示异步操作的最终结果(成功或失败)及其结果值。Promise有助于扁平化嵌套回调,并使异步代码更易于使用。

他们还支持使用 async/await 语法,进一步提高了异步 JavaScript 代码的可读性。通过 async/await,开发人员可以编写读起来与同步代码非常相似的异步代码,从而更轻松地理解数据流和事件。

然而,虽然这些工具使异步JavaScript更易于管理,但它们仍然存在一些问题。主要问题之一是通过事件循环传递代码时隐式上下文丢失。让我们在下一节中深入研究这一挑战。

异步上下文的挑战

正如我们所提到的,JavaScript的事件循环 - 结合Promise和async/await - 允许开发人员编写异步代码,比嵌套回调更容易管理和理解。但是,这些工具存在自己的困难:通过事件循环传递代码时,来自调用站点的某些隐式信息会丢失。

这个“隐含信息”是什么?在同步代码执行中,值在执行的生命周期内始终可用。它们可以显式传递,如函数参数或封闭变量(在外部作用域中定义,但由内部函数引用),也可以隐式传递,例如存储在多个作用域可访问的变量中的值。

但是,当涉及到异步执行时,情况会发生变化。让我们用一个例子来说明这一点。假设您有一个在不同函数作用域之间共享的变量,并且其值在异步函数执行期间发生变化。

在函数执行开始时,共享变量可能具有一定的值。但是,当函数完成时(例如, await 在操作之后),该共享变量的值可能已更改,从而导致潜在的意外结果。

这就是在异步 JavaScript 中丢失隐式调用站点信息的问题出现的地方。无论您使用的是Promise链还是async/await,事件循环的行为都保持不变。

但是,当事件循环执行异步代码时,调用堆栈(跟踪函数调用以及函数调用后返回位置的机制)会被修改。这意味着在原始调用站点上隐式提供的信息可能会在异步函数完成时丢失。

async/await 语法的引入虽然总体上是一个积极的变化,但使这个问题进一步复杂化。由于 async/await 使异步代码看起来与同步代码非常相似,因此更难看到调用堆栈和上下文被修改的位置。这可能会导致错误和不可预测的行为,以及难以追踪的意外错误。

认识到这个问题,TC39委员会成员Chengzhong Wu和Justin Ridgewell一直在寻求一种在异步操作中保留上下文的方法。他们的解决方案是异步上下文API。

什么是异步上下文 API?

异步上下文 API 是一种强大的新机制,用于处理异步上下文提案中提出的异步 JavaScript 代码中的上下文。截至 2023 年 8 月,该提案目前处于 ECMAScript 标准化流程的第 2 阶段,这意味着它已被批准为提案草案,目前正在积极开发中。尽管该提案仍可能发生变化,但它被认为是包含在下一版 JavaScript 中的可行候选者。

异步上下文 API 将允许 JavaScript 通过事件循环在转换中捕获和使用隐式调用站点信息。它允许开发人员编写与同步代码非常相似的异步代码,即使在需要隐式信息的情况下也是如此。

此 API 围绕两个基本构造展开: AsyncContext.Variable 和 AsyncContext.Snapshot 。这些构造允许开发人员在异步代码块中创建、操作和检索上下文变量,从而提供通过异步代码(如 Promise 延续或异步回调)传播值的机制。

AsyncContext.Variable 类

该 AsyncContext.Variable 类允许您创建一个可以在异步上下文中设置和访问的新变量。该方法 run 在函数执行期间为变量设置值,而 get 该方法检索变量的当前值。

AsyncContext.Snapshot 类

另一方面,该 AsyncContext.Snapshot 类使您能够捕获给定 AsyncContext.Variable 时间所有实例的当前值。这就像拍摄所有上下文变量状态的“快照”,然后可以使用这些快照在以后使用这些捕获的值执行函数。

这些新结构有望大大简化异步JavaScript中的上下文管理。借助异步上下文 API,开发人员可以保持一致对所需隐式信息的访问,无论他们执行多少异步操作。

使用 AsyncContext

让我们看一下异步上下文提案 AsyncContext 中的命名空间声明:

namespace AsyncContext {
  class Variable {
    constructor(options: AsyncVariableOptions);

    get name(): string;

    run(value: T, fn: (...args: any[])=> R, ...args: any[]): R;

    get(): T | undefined;
  }

  interface AsyncVariableOptions {
    name?: string;
    defaultValue?: T;
  }

  class Snapshot {
    constructor();

    run(fn: (...args: any[]) => R, ...args: any[]): R;
  }
}

AsyncContext 命名空间引入了几个基本类和一个接口:

  • Variable :此类表示可以保存值的上下文变量;泛型类型  指示变量可以保存任何数据类型。它还有一个 run() 设置变量值并执行函数的方法,其 get() 方法返回变量的当前值
  • AsyncVariableOptions :这是一个接口,用于定义可以传递给构造函数的 Variable 可能选项。它可以包含变量的名称和默认值
  • Snapshot :此类捕获特定 AsyncContext.Variables 时刻的所有状态。它有一个 run() 方法,该方法使用拍摄快照时的变量值执行函数

理解 AsyncContext.Variable

让我们更深入地了解工作原理 AsyncContext.Variable 。假设您有一个 asyncVar 的实例 AsyncContext.Variable 。若要在函数执行期间设置 其 asyncVar 值,请使用 run 该方法。此方法将以下内容作为其前两个参数:

  • 要设置的值 asyncVar
  • 其执行 asyncVar 应保存该值的函数
  • 下面是一个示例:

    const asyncVar = new AsyncContext.Variable()
    asyncVar.run('top', main)

    在此代码中, asyncVar 在 main 函数执行期间设置为字符串“top”。然后可以使用 在 main asyncVar.get() 函数中访问此值。请注意,通过 AsyncContext API, AsyncContext.Variable 实例旨在跨同步和异步操作(例如使用 setTimeout 。

    有趣的是, AsyncContext.Variable 运行可以嵌套。这意味着您可以在已运行的函数 asyncVar 中调用该方法, run 并设定了 的值 asyncVar 为 。在这种情况下,在内部函数执行期间采用新值, asyncVar 然后在内部函数完成后恢复为以前的值。

    理解 AsyncContext.Snapshot

    该 AsyncContext.Snapshot 类用于捕获特定时间点所有 AsyncContext.Variable 实例的状态。这是通过调用构造函数来完成的 Snapshot 。快照稍后可用于运行函数,就好像捕获的值仍然是其各自变量的当前值一样。这是通过 Snapshot 实例的方法完成 run 的。

    让我们看一个例子:

    asyncVar.run('top', () => {
      const snapshotDuringTop = new AsyncContext.Snapshot()
    
      asyncVar.run('C', () => {
        snapshotDuringTop.run(() => {
          console.log(asyncVar.get()) // => 'top'
        })
      })
    })

    在此代码中, 生成 snapshotDuringTop 创建时所有 AsyncContext.Variable 实例状态的快照。稍后,在asyncVar的方法中 run ,使用 run.snapshotDuringTop的asyncVar方法。

    即使此时设置为“C”,在 asyncVar snapshotDuringTop.run() 执行中,的值 asyncVar 仍然是“top”,因为这是创建时 snapshotDuringTop 的值。

    异步上下文 API 的影响

    提议的Async Context API对于JavaScript开发人员具有巨大的潜力,特别是那些使用Node.js等服务器端JavaScript环境的开发人员。此 API 将使开发人员能够更有效地处理异步代码中的上下文,提供在执行的特定点设置、检索和“冻结”上下文的函数。

    这可能会使开发人员摆脱在异步操作中管理上下文的复杂性,并大大增强可跟踪性和调试,从而可以更好地专注于编写有效和高效的代码。

    更实际地说,如果实现,异步上下文可以显着增强各种用例,例如:

    • 使用与异步调用堆栈相关的信息批注日志
    • 跨逻辑异步控制线程收集性能信息
    • 提供对应用程序性能的准确洞察

    它还可以通过在整个请求处理过程中维护上下文来改进 Web API 的功能,例如优先级任务计划,甚至跨多个异步操作。

    此外,异步上下文 API 可以帮助改进错误处理,使开发人员能够更准确地将异常跟踪到其原始上下文,从而缩短解决时间。它还可以通过跨异步边界管理和维护上下文来提供更有效的并发控制。

    在更大的范围内,Async Context提案代表了JavaScript持续发展的重要进步,特别是对于异步编程。通过解决异步编程带来的一些独特挑战,它可以提供一个强大、灵活的解决方案。然而,该提案仍处于早期阶段,其最终影响将取决于其在 JavaScript 社区中的最终实现和采用。

    写在最后

    如果实现,异步上下文 API 将为开发人员提供更强大的工具来管理异步代码中的上下文。在异步环境中传播上下文的能力可能是变革性的,从而带来更准确的跟踪、调试和性能监视。

    JavaScript 的未来可能会继续强调异步操作的有效处理,而像 Async Context 这样的提案代表了迈向未来的必要步骤。

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论