阅读 579

JavaScript异步处理的那些事儿

前言

原文

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

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

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

没有异步处理

先看一段代码:

// 假设有一个耗时的异步请求 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()

异步的解决方案

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 中去,并在异步请求结束后执行回调函数里的操作。

问题似乎已经解决了??但是,如果有多个异步请求,并且在每个异步请求完成后都执行一些操作,那代码就会像下面这样:

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,然后还有请求3、请求4、请求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,只能说。。。

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

关注下面的标签,发现更多相似文章
评论