你可能知道,Javascript语言的执行环境是”单线程”(single thread)。
所谓”单线程”,就是指一次只能完成一件任务。如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务,以此类推。
这种模式的好处是实现起来比较简单,执行环境相对单纯;坏处是只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。常见的浏览器无响应(假死),往往就是因为某一段Javascript代码长时间运行(比如死循环),导致整个页面卡在这个地方,其他任务无法执行。
为了解决这个问题,Javascript 语言将任务的执行模式分成两种:同步(Synchronous)和异步(Asynchronous)。

本文列举 6 种异步编程的方法

  • 回调事件
  • 事件监听
  • 发布/订阅
  • Promise
  • Generate
  • async/await

    一、回调函数

    这是异步编程最基本的方法。 ```javascript // setTimeout(callback, delay) 这个方法允许我们传入一个回调函数 const callback = () => console.log(‘I am callback’)

setTimeout(callback)

  1. ```javascript
  2. /**
  3. * 函数 asyncToSomething 接收 callback 回调函数
  4. * 在适当的时候,执行该回调事件
  5. * @param {Function} callback
  6. */
  7. const asyncToSomething = (callback) => {
  8. // 用 setTimeout 代指异步事件
  9. setTimeout(() => {
  10. const data = {}
  11. // do something
  12. // ...
  13. callback && callback(data)
  14. }, 1000)
  15. console.log('asyncToSomething')
  16. }
  17. // 作为回调函数
  18. const todo = (data) => {
  19. console.log(data)
  20. }
  21. console.log(1)
  22. asyncToSomething(todo)
  23. console.log(2)
  24. console.log(3)

打印顺序:1 ——> asyncToSomething ——> 2 ——> 3 ——> {}

:::info 1 打印完,asyncToSomething 函数是同步执行的,因此打印了 asyncToSomething
todo 函数虽然需要执行,但它在 setTimeout 中,不会阻塞 JS 继续执行。
于是打印 2、3,最后再打印 {}

这个 todo 函数作为回调函数,在 asyncToSomething 函数中的异步事件 setTimeout 执行过程中执行。 ::: 回调函数的缺点:回调函数层层嵌套,变成“ 回调地狱 ”,不雅观,逻辑复杂时不容易维护。

  1. setTimeout(() => {
  2. setTimeout(() => {
  3. setTimeout(() => {
  4. setTimeout(() => {
  5. setTimeout(() => {
  6. setTimeout(() => {})
  7. })
  8. })
  9. })
  10. })
  11. }, 1000)

二、事件监听

另外一种异步编程的思想是采用事件驱动模式。当某个事件发生时,监听该事件的函数就会执行。

  1. const body = document.body
  2. const clickHandler = () => console.log('click')
  3. // 监听了 body 的 click 事件。
  4. // 当触发 click 时,执行 clickHandler
  5. body.addEventListener('click', clickHandler)
  1. /**
  2. * 装饰器:为 object 添加事件监听机制
  3. * @param {any} object
  4. */
  5. function addListen(object) {
  6. const eventHandler = {}
  7. const proto = Object.getPrototypeOf(object)
  8. const addEventListener = function (eventType, callback) {
  9. if (eventHandler[eventType]) eventHandler[eventType].push(callback)
  10. else eventHandler[eventType] = [callback]
  11. }
  12. const removeEventListener = function (eventType, callback) {
  13. const index = eventHandler[eventType].indexOf(callback)
  14. if (index !== -1) eventHandler[eventType].splice(index, 1)
  15. }
  16. const trigger = function (eventType, ...args) {
  17. eventHandler[eventType].forEach((callback) => callback(...args))
  18. }
  19. Object.setPrototypeOf(object, {
  20. ...proto,
  21. eventHandler,
  22. addEventListener,
  23. removeEventListener,
  24. trigger
  25. })
  26. return object
  27. }
  28. const data = Object.create(null)
  29. addListen(data)
  30. data.addEventListener('change', function () {
  31. console.log(111)
  32. })
  33. data.trigger('change')

三、发布/订阅

这种方法的性质与”事件监听”类似。而发布/订阅模式,可以让我们通过查看“ 消息中心 ”,了解存在多少信号、每个信号有多少订阅者,从而监控程序的运行。

  1. class MessageCenter {
  2. constructor() {
  3. this.center = {}
  4. }
  5. // 注册事件
  6. subscribe(type, callback) {
  7. if (!this.center[type]) this.center[type] = [callback]
  8. else this.center[type].push(callback)
  9. }
  10. // 注销事件
  11. unSubscribe(type, callback) {
  12. const index = this.center[type].indexOf(callback)
  13. if (index !== -1) this.center[type].splice(index, 1)
  14. }
  15. // 发布消息
  16. public(type, ...args) {
  17. if (this.center[type]) this.center[type].forEach(callback => callback(...args))
  18. }
  19. }
  20. // 测试用例
  21. const messageCenter = new MessageCenter()
  22. function changeHandler(...args) {
  23. console.log(...args)
  24. }
  25. messageCenter.subscribe('change', changeHandler)
  26. function asyncTodoSomething() {
  27. setTimeout(() => {
  28. messageCenter.public('change', 456)
  29. }, 500)
  30. }
  31. asyncTodoSomething()

四、Promise

Promise 对象是 CommonJS 工作组提出的一种规范,目的是为异步编程提供统一接口
它的思想是,每一个异步任务返回一个 Promise 对象,该对象有一个 then 方法,允许指定回调函数。比如,f1 的回调函数 f2,可以写成:

  1. f1().then(f2)

五、Generator

六、async/await

参考资料

《Javascript异步编程的4种方法》