• 售前

  • 售后

热门帖子
入门百科

如何在现代JavaScript中编写异步任务

[复制链接]
易网随缘倚 显示全部楼层 发表于 2021-10-25 20:00:07 |阅读模式 打印 上一主题 下一主题
前言

在本文中,我们将探究过去异步执行的 JavaScript 的演变,以及它是怎样改变我们编写代码的方式的。我们将从最早的 Web 开发开始,不停到当代异步模式。
作为编程语言, JavaScript 有两个重要特性,这两个特性对于明确我们的代码怎样工作非常重要。首先是它的同步特性,这意味着代码将逐行运行,其次是单线程,任何时间都仅执行一个命令。
随着语言的发展,答应异步执行的新工件出如今场景中。开发人员在办理更复杂的算法和数据流时实验了差别的方法,从而导致新的接口和模式出现。
同步执行和观察者模式


如简介中所述,JavaScript 通常会逐行运行你编写的代码。即使在最初的几年中,该语言也有这种规则的破例,只管很少,你可能已经知道了它们:HTTP 哀求,DOM 变乱和time interval。
如果我们通过添加变乱侦听器去相应用户对元素的单击,则无论语言解释器在运行什么,它都会停止,然后运行在侦听器回调中编写的代码,之后再返回正常的流程。
与 interval 或网络哀求雷同,addEventListener,setTimeout 和 XMLHttpRequest 是 Web 开发人员访问异步执行的第一批工件。
只管这些是 JavaScript 中同步执行的破例环境,但重要的是你要相识该语言仍然是单线程的。我们可以冲破这种同步性,但是解释器仍然每次运行一行代码。
比方查抄一个网络哀求。
  1. var request = new XMLHttpRequest();
  2. request.open('GET', '//some.api.at/server', true);
  3. // observe for server response
  4. request.onreadystatechange = function() {
  5. if (request.readyState === 4 && xhr.status === 200) {
  6. console.log(request.responseText);
  7. }
  8. }
  9. 11request.send();
复制代码
不管发生什么环境,当服务器恢复运行时,分配给 onreadystatechange 的方法都会在取回程序的代码序列之前被调用。
对用户交互做出反应时,也会发生类似的环境。
  1. const button = document.querySelector('button');
  2. // observe for user interaction
  3. button.addEventListener('click', function(e) {
  4. console.log('user click just happened!');
  5. })
复制代码
你可能会注意到,我们正在毗连一个外部变乱并传递一个回调,告诉代码当变乱发生时应该怎么做。十多年前,“什么是回调?”是一个非常受期待的面试问题,由于在许多代码库中随处都有这种模式。
在上述每种环境下,我们都在相应外部变乱。不管是到达肯定的时间隔断、用户操纵还是服务器相应。我们本身无法创建异步任务,我们总是 观察 发生在我们力所能及范围之外的变乱。
这就是为什么这种方式的代码被称为观察者模式的原因,在这种环境下,它最好由 addEventListener 接口来表现。很快,袒露这种模式的变乱发送器库或框架开始发达发展。
NODE.JS 和变乱发送器


Node.js 是一个很好的例子,它的官网把本身形貌为“异步变乱驱动的 JavaScript 运行时”,所以变乱发送器和回调是一等公民。它乃至已经实现了一个 EventEmitter 构造函数。
  1. const EventEmitter = require('events');
  2. const emitter = new EventEmitter();
  3. // respond to events
  4. emitter.on('greeting', (message) => console.log(message));
  5. // send events
  6. emitter.emit('greeting', 'Hi there!');
复制代码
这不仅是通用的异步执行方法,而且是其生态系统的核心模式和惯例。Node.js 开发了一个在差别环境中乃至在 web 之外编写 JavaScript 的新时代。固然异步的环境也是可能的,比方创建新目次或写文件。
  1. const { mkdir, writeFile } = require('fs');
  2. const styles = 'body { background: #ffdead; }';
  3. mkdir('./assets/', (error) => {
  4. if (!error) {
  5. writeFile('assets/main.css', styles, 'utf-8', (error) => {
  6.   if (!error) console.log('stylesheet created');
  7. })
  8. }
  9. })
复制代码
你可能会注意到,回调函数将第一个参数接作为 error ,如果得到了预期的相应数据,则将其作为第二个参数。这就是所谓的错误优先回调模式,它成为作者和贡献者为包和库所做的约定。
Promise 和没完没了的回调链


随着 Web 开发面临的更复杂的问题,出现了对更好的异步工件的需求。如果我们查看最后一个代码段,则会看到重复的回调链,随着任务数目标增加,回调链的扩展效果不佳。
比方,我们仅添加两个步骤,即文件读取和样式预处理。
  1. const { mkdir, writeFile, readFile } = require('fs');
  2. const less = require('less')
  3. readFile('./main.less', 'utf-8', (error, data) => {
  4. if (error) throw error
  5. less.render(data, (lessError, output) => {
  6. if (lessError) throw lessError
  7. mkdir('./assets/', (dirError) => {
  8.   if (dirError) throw dirError
  9.   writeFile('assets/main.css', output.css, 'utf-8', (writeError) => {
  10.   if (writeError) throw writeError
  11.   console.log('stylesheet created');
  12.   })
  13. })
  14. })
  15. 16})
复制代码
我们可以看到,由于多个回调链和重复的错误处理,编写程序变得越来越复杂,代码变得更加难以明确。
Promise、包装和链模式
当 Promises 最初被宣布为 JavaScript 语言的新成员时,并没有引起太多关注,它们并不是一个新概念,由于其他语言在几十年前就已经实现了类似的实现。毕竟上自从它出现以来,他们就改变了我从事的大多数项目标语义和结构。
Promises不仅为开发人员引入了用于编写异步代码的内置办理方案,,而且还开发了Web 开发的新阶段,成为 Web 规范厥后的新功能(如 fetch)的构建底子。
从回调方法迁移到基于 promise 的方法在项目(比方库和浏览器)中变得越来越广泛,乃至 Node.js 也开始迟钝地迁移到它上面。
比方,包装 Node 的 readFile 方法:
  1. const { readFile } = require('fs');
  2. const asyncReadFile = (path, options) => {
  3. return new Promise((resolve, reject) => {
  4.   readFile(path, options, (error, data) => {
  5.    if (error) reject(error);
  6.    else resolve(data);
  7.   })
  8. });
  9. }
复制代码
在这里,我们通过在 Promise 构造函数内部执行来隐藏回调,方法乐成后调用 resolve,界说错误对象时调用reject。
当一个方法返回一个  Promise  对象时,我们可以通过将一个函数传递给 then 来依照其乐成的解析,它的参数是 Promise  被解析的值,在这里是 data。
如果在方法运行期间抛堕落误,则将调用 catch 函数(如果存在)。
注意:如果你需要更深入地相识 Promise 的工作原理,发起你看 Jake Archibald 在 Google 的 web 开发博客上写的文章“ JavaScript Promises:简介”。
如今我们可以使用这些新方法并避免回调链。
  1. asyncRead('./main.less', 'utf-8')
  2. .then(data => console.log('file content', data))
  3. .catch(error => console.error('something went wrong', error))
复制代码
它具有创建异步任务的原生方法,并以清楚的接口跟踪其可能的效果,这摆脱了观察者模式。基于 Promise 的代码似乎可以办理可读性差且轻易堕落的代码。
在更好的语法突出显示和更清楚的错误提示信息对编码过程中提供的帮助下,对于开发人员来说,编写更轻易明确的代码变得更具可猜测性,而且执行的环境更好,更轻易发现可能的陷阱。
Promises 的接纳在社区中非常广泛,以至于 Node.js 敏捷发布其 I/O 方法的内置版本以返回 Promise 对象,比方从 fs.promises 中导入文件操纵。
它乃至提供了一个 promisify 工具来包装依照错误优先回调模式的函数,并将其转换为基于 Promise 的函数。
但是 Promise 在所有环境下都能提供帮助吗?
让我们重新评估一下用 Promise 编写的样式预处理任务。
  1. const { mkdir, writeFile, readFile } = require('fs').promises;
  2. const less = require('less')
  3. readFile('./main.less', 'utf-8')
  4. .then(less.render)
  5. .then(result =>
  6.   mkdir('./assets')
  7.    .then(writeFile('assets/main.css', result.css, 'utf-8'))
  8. )
  9. .catch(error => console.error(error))
复制代码
代码中的冗余显着减少了,尤其是在错误处理方面,由于我们如今依赖于 catch,但是 Promise 在某种程度上没能提供直接与动作串联干系的清楚代码缩进。
现实上,这是在调用 readFile 之后的第一个 then 语句中实现的。这些代码行之后发生的事情是需要创建一个新的作用域,我们可以在该作用域中先创建目次,然后将效果写入文件中。这会导致缩进节奏的停止,乍一看就不轻易确定指令序列。
注意:请注意,这是一个示例程序,我们可以控制某些方法,它们都依照行业惯例,但并非总是云云。通过更复杂的串联或引入差别的库,我们的代码风格可以轻松被冲破。
令人高兴的是,JavaScript 社区再次从其他语言的语法中学到了东西,并增加了一种表现方法,可以在大多数环境下帮助异步任务串联,而不是像同步代码那样能够令人轻松的阅读。
Async 与 Await


Promise 被界说为执行时的未办理的值,创建 Promise 实例是对此工件的“显式”调用。
  1. const { mkdir, writeFile, readFile } = require('fs').promises;
  2. const less = require('less')
  3. readFile('./main.less', 'utf-8')
  4. .then(less.render)
  5. .then(result =>
  6.   mkdir('./assets')
  7.    .then(writeFile('assets/main.css', result.css, 'utf-8'))
  8. )
  9. .catch(error => console.error(error))
复制代码
在异步方法内部,我们可以用 await 保留字来确定 Promise 的办理方案,然后再继续执行。
让我们用这种语法重新编写代码段。
  1. const { mkdir, writeFile, readFile } = require('fs').promises;
  2. const less = require('less')
  3. async function processLess() {
  4. const content = await readFile('./main.less', 'utf-8')
  5. const result = await less.render(content)
  6. await mkdir('./assets')
  7. await writeFile('assets/main.css', result.css, 'utf-8')
  8. }
  9. 11processLess()
复制代码
注意:请注意,我们需要将所有代码移至某个方法中,由于我们无法在 异步函数的作用域之外使用 await 。
每当异步方法找到一个 await 语句时,它将停止执行,直到 promise 被办理为止。
只管是异步执行,但用 async/await 表现会使代码看起来好像是同步的,这是轻易被开发人员阅读和明确的东西。
那么错误处理呢?我们可以用在语言中存在了很久的try 和 catch。
  1. const { mkdir, writeFile, readFile } = require('fs').promises;
  2. const less = require('less')
  3. async function processLess() {
  4. const content = await readFile('./main.less', 'utf-8')
  5. const result = await less.render(content)
  6. await mkdir('./assets')
  7. await writeFile('assets/main.css', result.css, 'utf-8')
  8. }
  9. try {
  10. processLess()
  11. } catch (e) {
  12. console.error(e)
  13. }
复制代码
我们大可放心,在过程中抛出的任何错误都会由 catch 语句中的代码处理。如今我们有了一个易于阅读和规范的代码。
对返回值举行的后续操纵无需存储在不会破坏代码节奏的 mkdir 之类的变量中;也无需在以后的步骤中创建新的作用域来访问 result 的值。
可以肯定地说,Promise 是该语言中引入的基本工件,对于在 JavaScript 中启用 async/await 表现法是必须的,你可以在当代浏览器和最新版本的 Node.js 中使用它。
注意:最近在 JSConf 中,Node 的创建者和第一贡献者 Ryan Dahl, 对在其早期开发中没有遵守Promises 表现遗憾,重要是由于 Node 的目标是创建变乱驱动服务器和文件管理,而 Observer 模式更得当如许。
结论


将 Promise 引入 Web 开发的目标是改变我们在代码中次序操纵的方式,并改变了我们明确代码的方式以及编写库和包的方式。
但是摆脱回调链更难办理,我认为在多年来风俗于观察者模式和接纳的方法之后,必须将方法传递给 then 并不能帮助我们摆脱原有的思绪,比方 Node.js。
正如 Nolan Lawson 在他的出色文章“关于 Promise 级联的错误使用“【https://pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html】 中所述,旧的回调风俗是死硬且顽固的!在文中他解释了怎样避免这些陷阱。
我认为 Promise 是中心步骤,它答应以自然的方式天生异步任务,但并没有帮助我们进一步改进更好的代码模式,偶尔你需要更顺应改进的语言语法。
当实验使用JavaScript办理更复杂的困难时,我们看到了对更成熟语言的需求,而且我们实验了从前不曾在网上看到的体系结构和模式。
我们仍然不知道 ECMAScript 规范在几年后的样子,由于我们不停在将 JavaScript 治理扩展到 web 之外,并实验办理更复杂的困难。
如今很难说我们需要从语言中真正地将这些困难转酿成更简单的程序,但是我对 Web 和 JavaScript 本身怎样推动技能,试图顺应寻衅和新环境感到满意。与十年前刚刚开始在浏览器中编写代码时相比,我以为如今 JavaScript 是“异步友好”的。
原文:https://www.smashingmagazine.com/2019/10/asynchronous-tasks-modern-javascript/

到此这篇关于如安在当代JavaScript中编写异步任务的文章就介绍到这了,更多干系JavaScript编写异步任务内容请搜刮草根技能分享从前的文章或继续浏览下面的干系文章渴望大家以后多多支持草根技能分享!

帖子地址: 

回复

使用道具 举报

分享
推广
火星云矿 | 预约S19Pro,享500抵1000!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

草根技术分享(草根吧)是全球知名中文IT技术交流平台,创建于2021年,包含原创博客、精品问答、职业培训、技术社区、资源下载等产品服务,提供原创、优质、完整内容的专业IT技术开发社区。
  • 官方手机版

  • 微信公众号

  • 商务合作