JavaScript异步处理的那些事儿

前言

原文git

以前总结了关于 JavaScript 异步的 事件循环与消息队列 机制以及 ES6 带来的 微任务与宏任务 的知识。传送门github

下面是关于JS异步处理的各类方案:ajax

callback >> ES6 Primise >> async/await
复制代码

没有异步处理

先看一段代码:bash

// 假设有一个耗时的异步请求 ajax,在 2 秒后打印日志

function ajax () {
  // do something...
  setTimeout(() => {
    console.log('Hello, Zavier Tang!')
  }, 2000)
}
ajax()
// do something...
console.log('The end.')

// The end.
// Hello, Zavier Tang!
复制代码

这里模拟了一个简单的异步网络请求,并在 2 秒后打印 "Hello, Zavier Tang!",而后作一些处理("do something")后,打印结束 "The end."。网络

结果是先打印的 "The end."。异步

显然这并非咱们要的结果,"异步请求" ajax 中的 setTimeout 并无阻塞代码的执行,而是直接执行了 console.log()async

异步的解决方案

1. 传统(可怕)的 callback 回调地狱

一样是上面的异步网络请求,这里使用 callback 回调函数的方式解决 JavaScript 同步带来的问题。函数

function ajax (fn) {
  // do something...
  setTimeout(() => {
    console.log('Hello, Zavier Tang!')
    fn()
  }, 2000)
}
ajax(() => {
  // do something...
  console.log('The end.')
})

// Hello, Zavier Tang!
// The end.
复制代码

这里咱们直接把异步请求以后要作的一些操做作为回调函数,传递到 ajax 中去,并在异步请求结束后执行回调函数里的操做。post

问题彷佛已经解决了??可是,若是有多个异步请求,而且在每一个异步请求完成后都执行一些操做,那代码就会像下面这样:学习

function ajax (fn) {
  // do something...
  setTimeout(() => {
    console.log('Hello, Zavier Tang!')
    fn()
  }, 500)
}

ajax(() => {
  // do something...
  console.log('The end 1.')
  ajax(() => {
    // do something...
    console.log('The end 2.')
    ajax(() => {
      // do something...
      console.log('The end 3.')
      ajax(() => {
        // do something...
        console.log('The end 4.')
        ajax(() => {
          // do something...
          console.log('The end 5.')
          // ......
          // ......
        })
      })
    })
  })
})

// Hello, Zavier Tang!
// The end 1.
// Hello, Zavier Tang!
// The end 2.
// Hello, Zavier Tang!
// The end 3.
// Hello, Zavier Tang!
// The end 4.
// Hello, Zavier Tang!
// The end 5.
复制代码

看起来很吓人!!

若是是这样:请求1结束后进行请求2,而后还有请求三、请求四、请求5,而且请求2还可能依赖于请求1的数据,请求3依赖于请求2的数据,不停的一层一层嵌套,对于错误的处理也极不方便,因此称之为 callback 回调地狱。

2. 下一代异步解决方案:Promise

ES6 的 Promise 被称为 JS 异步的下一代解决方案。

关于 Promise:

  • 用于异步计算。
  • 能够将异步操做队列化,按照指望的顺序执行,返回符合预期的结果。
  • 能够在对象之间传递和操做 Promise,方便处理队列。

接下来使用 Promise 处理异步:

function ajax (word) {
  return new Promise((resolve) => {
    // do something...
    setTimeout(() => {
      resolve('Hello ' + word)
    }, 500)
  })
}

ajax('请求1')
  .then((word) => {
    console.log(word)
    console.log('The end 1.')
    return ajax('请求2')
  })
  .then((word) => {
    console.log(word)
    console.log('The end 2.')
    return ajax('请求3')
  })
  .then((word) => {
    console.log(word)
    console.log('The end 3.')
  })
  // .catch(() => {})

// Hello 请求1
// The end 1.
// Hello 请求2
// The end 2.
// Hello 请求3
// The end 3.
复制代码

上面仍是连续的异步请求,每次请求后打印日志。这样看起来比上面的回调地狱舒服多了,每一个请求经过链式的调用,在最后能够对全部的请求进行错误处理。

但,彷佛还并非特别优雅。

3. 终极解决方案:async/await

关于 async/await 的简介:

  • async/await 是写异步代码的新方式,不一样于之前的 callback 回调函数和 Promise。
  • async/await 是基于 Promise 实现的,不能用于普通的回调函数。
  • async/await 与 Promise 同样,是非阻塞的。
  • async/await 使得异步代码看起来像同步代码。

体验一下神奇的 async/await:

// 接上面的代码
async function doAsync () {
  const word1 = await ajax('请求1')
  console.log(word1)
  console.log('The end 1')

  const word2 = await ajax('请求2')
  console.log(word2)
  console.log('The end 2')

  const word3 = await ajax('请求3')
  console.log(word3)
  console.log('The end 3')
}
doAsync()

// Hello 请求1
// The end 1
// Hello 请求2
// The end 2
// Hello 请求3
// The end 3
复制代码

这样看起来,更优雅了。没有任何括号,也没有 callback,没有 then,直接申明 async,使用 await 等待异步执行完成,看起来也更像是同步的代码。

总结

JavaScript 的异步编写方式,从 callback 回调函数到 Promise ,再到 async/await,只能说。。。

技术发展太快啦,赶忙学习吧!