处理异步事件的三种方式

更新日期: 2020-10-22阅读: 1.4k标签: 异步

网站开发中,异步事件是项目必然需要处理的一个环节,也因为前端框架的兴起,通过框架实现的 SPA 已经是快速建构网站的标配了,一部获取数据也就成了不可或缺的一环;本文来就讲一讲 JavaScript 中异步的处理方式。


同步?异步?

首先当然要先理解一下同步及异步分别是指什么。

这两个名词对于初学者来说总是让人感到困惑的,毕竟从中文字面上的意思很容易让人反过来理解,从信息科学的角度来说,同步 指的是一件一件做事,而异步则是很多事情在一起并行的处理。

比如我们去银行办理业务,在窗口前排队就是同步执行,而拿到号码先去做别的事情的就是异步执行;通过 Event Loop 的特性,在 JavaScript 处里异步事件可说是轻而易举的

那么在 JavaScript 中处理异步事件的方法是什么呢?


回调函数

我们最熟悉最的就是回调函数了。例如网页与用户进行互动时注册的事件监听器,就需要接收一个回调函数;或是其他 Web api 的各种功能如 setTimeout、xhr,也都能通过传递回调函数在用户要求的时机去触发。先看一个 setTimeout 的例子:

// callback
function withCallback() {
  console.log('start')
  setTimeout(() => {
    console.log('callback func')
  }, 1000)
  console.log('done')
}withCallback()
// start
// done
// callback func

在 setTimeout 被执行后,当过了指定的时间间隔之后,回调函数会被放到队列的末端,再等待事件循环处理到它。

注意:也就时因为这种机制,开发者设定给 setTimeout 的时间间隔,并不会精准的等于从执行到触发所经过的时间,使用时要特别注意!

回调函数虽然在开发中十分常见,但也有许多难以避免的问题。例如由于函数需要被传递给其他函数,开发者难以掌控其他函数内的处理逻辑;又因为回调函数仅能配合 try … catch 捕捉错误,当异步错误发生时难以控制;另外还有最著名的“回调地狱”。


Promise

幸好在 ES6 之后出现了 Promise,拯救了身陷在地狱的开发者们。其基本用法也很简单:

function withPromise() {
  return new Promise(resolve => {
    console.log('promise func')
    resolve()
  })
}
withPromise()
  .then(() => console.log('then 1'))
  .then(() => console.log('then 2'))
// promise func
// then 1
// then 2

之前讨论 Event Loop 时没有提到的是,在HTML 5 的Web API 标准 中,Event Loop 新增了微任务队列(micro task queue),而 Promise 正是通过微任务队列来驱动它的;微任务队列的触发时机是在栈被清空时,JavaScript 引擎会先确认微任务队列有没有东西,有的话就优先执行,直到清空后才从队列拿出新任务到栈上。

如上面的例子,当函数回传一个 Promise 时,JavaScript 引擎便会把后传入的函数放到微任务队列中,反复循环,输出了上列的结果。后续的 .then 语法会回传一个新的 Promise,参数函数则接收前一个 Promise.resolve 的结果,凭借这样函数参数传递,让开发者可以管道式的按顺序处理异步事件。

如果在例子中加上 setTimeout 就更能清楚理解微任务与一般任务的差别:

function withPromise() {
  return new Promise(resolve => {
    console.log('promise func')
    resolve()
  })
}
withPromise()
  .then(() => console.log('then 1'))
  .then(() => setTimeout(() => console.log('setTimeout'), 0))
  .then(() => console.log('then 2'))
// promise func
// then 1
// then 2 -> 微任务优先执行
// setTimeout

另外,前面所说的回调函数很难处理的异步错误,也可以通过 .catch 语法来捕获。

function withPromise() {
  return new Promise(resolve => {
    console.log('promise func')
    resolve()
  })
}
withPromise()
  .then(() => console.log('then 1'))
  .then(() => { throw new Error('error') })
  .then(() => console.log('then 2'))
  .catch((err) => console.log('catch:', err))
// promise func
// then 1
// catch: error
//   ...error call stack


async await

从 ES6 Promise 问世之后,异步代码从回呼地狱逐渐变成了优雅的函数式管道处理,但对于不熟悉度的开发者来说,只不过是从回调地狱变成了 Promise 地狱而已。

在 ES8 中规范了新的 async/await,虽然只是 Promise 和 Generator Function组合在一起的语法糖,但通过 async/await 便可以将异步事件用同步语法来处理,就好像是老树开新花一样,写起来的风格与 Promise 完全不同:

function wait(time, fn) {
  return new Promise(resolve => {
    setTimeout(() => {
      console.log('wait:', time)
      resolve(fn ? fn() : time)
    }, time)
  })
}
await wait(500, () => console.log('bar'))
console.log('foo')
// wait: 500
// bar
// foo

通过把 setTimeout 包装成 Promise,再用 await 关键字调用,可以看到结果会是同步执行的先出现 bar,再出现 foo,也就是开头提到的将异步事件写成同步处理。

再看一个例子:

async function withAsyncAwait() {
  for(let i = 0; i < 5; i++) {
    await wait(i*500, () => console.log(i))
  }
}await withAsyncAwait()
// wait: 0
// 0
// wait: 500
// 1
// wait: 1000
// 2
// wait: 1500
// 3
// wait: 2000
// 4

代码中实现了withAsyncAwait 函数,用 for 循环及 await 关键字反复执行 wait 函数;此处执行时,循环每次会按顺序等待不同的秒数再执行下一次循环。

在使用 async/await 时,由于 await 关键字只能在 async function 中执行,使用时务必要记得要同时使用。

另外在用循环处理异步事件时,需要注意在 ES6 之后提供的很多 Array 方法都不支持 async/await 语法,如果这里用 forEach 取代 for,结果会变成同步执行,每隔 0.5 秒就打印出数字。


总结

本文简单介绍了 JavaScript 处理异步的三种方式,并通过一些简单的例子说明代码的执行顺序;呼应前面提到的事件循环,再其中加入了微任务队列的概念。希望帮你理解同步和异步的应用。

    链接: https://www.fly63.com/article/detial/9845

    通过alert方法,去理解js中阻塞、局部作用域、同步/异步任务

    javascript中alert是Bom中的成员函数,alert对话框是模态的,具有阻塞性质的,不点击是不会执行后续代码的。js的阻塞是指在调用结果返回之前,当前线程会被挂起, 只有在得到结果之后才会继续执行。

    如何优化async代码?更好的编写async异步函数

    如何优化async代码?更好的编写async函数:使用return Promise.reject()在async函数中抛出异常,让相互之间没有依赖关系的异步函数同时执行,不要在循环的回调中/for、while循环中使用await,用map来代替它

    【JS】异步处理机制的几种方式

    Javascript语言的执行环境是单线程,异步模式非常重要。在浏览器端,耗时很长的操作都应该异步执行,避免浏览器失去响应,最好的例子就是Ajax操作。

    js异步加载方式有哪些?_详解异步加载js的多种方案

    js异步加载又被称为非阻塞加载,浏览器在下载JS的同时,还会进行后续页面处理。那么如何实现js异步加载呢?下面整理了多种实现方案供大家参考。异步加载js方案:Script Dom Element、onload时的异步加载、$(document).ready()、async属性、defer属性、es6模块type=module属性

    Nodejs 处理异步(获取异步数据并处理)的方法

    回调函数方式:将异步方法如readFile封装到一个自定义函数中,通过将异步方法得到的结果传给自定义方法的回调函数参数。事件驱动方式:使用node events模块,利用其EventEmitter对象

    JS常用的几种异步流程控制

    JavaScript引擎是基于单线程 (Single-threaded) 事件循环的概念构建的,同一时刻只允许一个代码块在执行,所以需要跟踪即将运行的代码,那些代码被放在一个任务队列 (job queue) 中

    前端异步编程之Promise和async的用法

    传统的异步解决方案采用回调函数和事件监听的方式,而这里主要记录两种异步编程的新方案:ES6的新语法Promise;ES2017引入的async函数;Generator函数(略)

    异步的JavaScript

    JS本身是一门单线程的语言,所以在执行一些需要等待的任务(eg.等待服务器响应,等待用户输入等)时就会阻塞其他代码。如果在浏览器中JS线程阻塞了,浏览器可能会失去响应,从而造成不好的用户体验。

    js 多个异步的并发控制

    请实现如下的函数,可以批量请求数据,所有的URL地址在urls参数中,同时可以通过max参数 控制请求的并发度。当所有的请求结束后,需要执行callback回调。发请求的函数可以直接使用fetch。

    解读react的setSate的异步问题

    将setState()认为是一次请求而不是一次立即执行更新组件的命令。为了更为可观的性能,React可能会推迟它,稍后会一次性更新这些组件。React不会保证在setState之后,能够立刻拿到改变的结果。

    点击更多...

    内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!