【译】HTML标准-8.1.6事件循环(三)

113 阅读6分钟

HTML 规范篇幅极长,阐述了很多前端技术的底层细节。我没有雄心通篇翻译,只打算挑部分感兴趣的内容。译文中如果存在表达不恰当的内容欢迎提出您宝贵的意见。

4. 通用任务源

在本规范和其他规范中,不出意外都会引用以下任务源:

  • DOM操作任务源:此任务源用于响应DOM操作,例如当元素插入文档时产生非阻塞的任务。
  • 用户操作任务源:此任务源用于响应用户交互,例如键盘或鼠标输入。响应用户输入的任务(例如单击事件)必须携带用户交互任务源入队。
  • 网络任务源:此任务源用于响应网络活动。
  • 历史穿梭任务源:此任务源用于对调用history.back()以及类似API的行为入队。

5. 处理其他规范中的事件循环

编写与事件循环正确交互的规范是很棘手的。而在规范中如何使用与并发模型无关的术语,使得这一点更加复杂,因此我们使用“事件循环”和“并行”,而没用更熟悉的模型中的特定术语,如“主线程”或“后台线程”。

默认情况下,规范中的内容通常是在事件循环之上运行。与事件循环处理模型不同,您看到的大多数算法都会落实到任务队列中的任务。

任何JavaScript的处理方法都是由调用该方法的用户代码调用。用户代码只能通过任务入队来运行,这些任务通常源于JavaScript处理器中的某处。

最重要的指导原则是,规范中需要执行的任何工作,如果可能阻塞事件循环,则必须异步执行。这包括(但不限于):

  • 执行繁重的计算
  • 显示一个面向用户的提示
  • 执行可能需要外部系统配合的操作(比如,退出进程)

下一个复杂问题是,在并行的算法中,不能创建或操作与具体的JavaScript作用域、全局对象或环境设置对象关联的对象。(用更熟悉的术语来说,您不能直接从后台线程访问主线程内存。)这样将会造成JavaScript代码可以观察到的数据竞争,因为毕竟,您的算法与JavaScript代码并行运行。

然而,您可以通过Infra操作规范层的数据结构和值,因为它们是作用域无关的。如果不进行特定转换(通常通过Web IDL),它们永远不会直接暴露于JavaScript。

译者注:因此,对于我们js用户可以理解为并行算法无法直接操作主线程内存

全局任务入队是基于任务入队算法的。不过一般来说,全局任务入队更好,因为它会自动选择正确的事件循环,和合适的document。较旧的规范通常将任务入队与隐式事件循环和隐式document概念结合使用,但不鼓励这样做。

综上所述,我们可以为需要并行工作的典型算法提供一个模板:

  1. 在事件循环中执行所有的同步工作。这可能包括将特定作用域的JavaScript值转换为作用域无关的规范级别值。
  2. 并行执行一组可能代价高昂的步骤,完全基于未知作用域的值进行操作,并生成未知作用域中的结果。
  3. 将指定任务源上的全局任务入队,并给定适当的全局对象,以将作用域无关的结果转换回事件循环中JavaScript对象的可观察的值。

以下是一种将输入的标量值字符串列表解析为URL后再对其进行“编码”的算法:

  1. 将 urls 赋值为 空数组

  2. 遍历每个输入字符串:

    1. 将 parsed 赋值为以当前设置对象解析的字符串的结果
    2. 如果解析失败,返回一个 rejected promise 携带一个“SyntaxError”DOMEException异常
    3. 将 serialized 赋值为经URL编码处理 parsed 的结果。
    4. 将 serialized 追加到 urls
  3. 将 realm 赋值为当前作用域变量集合

  4. 将 p 赋值为一个新的promise

  5. 并行执行以下步骤:

    1. 将 encryptedURLs 赋值为空数组

    2. 遍历urls,对每个url:

      1. 等待100毫秒,这样人们会以为我们在进行一个编码的长任务
      2. 将 encrypted 赋值为 url 派生的的字符串,截取url索引13字符向后字符串
      3. 将 encrypted 追加到 encryptedURLs
    3. 入队一个全局任务,绑定网络任务源,以 realm 作为全局对象,任务执行以下步骤:

      1. 将 array 赋值为将 encryptedURLs 转换为 JavaScript 数组的结果,存入 realm 作用域。
      2. promise 返回 array
  6. 返回 p 关于该算法,需要注意以下几点:

  • 在事件循环中提前进行URL解析,然后转到并行的步骤。这是必要的,因为解析依赖于当前环境对象,在并行进行之后,该对象将发生变化。
  • 或者也可以保存当前环境对象中 base URL API 的引用,并在并行步骤中使用,这是等效的处理。但是,我们建议您尽可能多地提前完成工作,就像本例所做的那样。试图保存多个值的引用可能容易出错;例如,如果我们只保存当前环境对象,而不是它的 base URL API ,那么可能会发生数据竞争。
  • 隐式地将字符串列表从初始步骤传递到并行步骤中的是可以的,因为列表和字符串都是作用域无关的。
  • 由于是在并行步骤中执行“昂贵的计算”(每个输入URL等待100毫秒),因此不会阻塞主事件循环。
  • promise 作为可观察的JavaScript对象,永远不会在并行步骤中创建和操作。p 在进入这些步骤之前创建,然后被特定目标的队列中的任务操作。
  • JavaScript 数组是在排队期间创建的,要小心指定在哪个作用域创建,因为这并不明显。 关于最后两点,请参见whatwg/webidl issue#135whatwg/webidl issue#371,我们仍在考虑上述 promise-resolution 模式的细节。)

另一件需要注意的事情是,如果该算法是由一个具体的Web IDL操作调用的,接受一个<USVString>序列,则会自动将作者提供的特定作用域JavaScript对象转换为作用域未知的<USVString>序列Web IDL类型,然后将其视为合规码元字符串列表。因此,根据规范的结构,在主事件循环中可能会发生其他隐式步骤,这些步骤在转入并行的整个过程中生效。

总结

后两个小节阐述了:

  • 各规范间的通用任务源
  • 利用事件循环编写代码的原则
  • 编写并行算法的需要注意的问题、模板、示例 这一部分的内容聚焦于参与事件循环的上次应用,指导读者利用事件循环。js开发者编写多线程算法可以参考。