JavaScript异步发展历史

前言

在 MDN 的 JavaScript 系列中咱们已经学习了 callback、promise、generator、async/await。而在这一篇文章中,做者将以实际样例阐述异步发展历史,介绍每种实现方式的优点与不足,以期帮助读者熟悉历史进程并把握异步发展的脉络。 异步发展历史javascript

异步

几十年前的导航网站,清爽又简单,没有什么特别的功能,只是单纯的展现,现成的网页在服务器上静静躺着,高效毫无压力,让人很喜欢。html

几十年后的今天,静态页面远不能知足用户的需求,网站变得复杂起来,用户交互愈来愈频繁,从而产生大量复杂的内部交互,为了解决这种复杂,出现了各类系统“模式”,从而很容易的在外部获取数据,并实时展现给用户。java

获取外部数据实际上就是“网络调用”,这个时候“异步”这个词汇出现了。编程

异步指两个或两个以上的对象或事件不一样时存在或发生(或多个相关事物的发生无需等待其前一事物的完成)

image-20210116210456133

异步 callbacks

异步 callbacks其实就是函数,只不过是做为参数传递给那些在后台执行的其余函数。当那些后台运行的代码结束,就调用 callbacks 函数,通知你工做已经完成,或者其余有趣的事情发生了。

场景segmentfault

let readFile = (path, callBack) => {
  setTimeout(function () {
    callBack(path)
  }, 1000)
}

readFile('first', function () {
  console.log('first readFile success')
  readFile('second', function () {
    console.log('second readFile success')
    readFile('third', function () {
      console.log('third readFile success')
      readFile('fourth', function () {
        console.log('fourth readFile success')
        readFile('fifth', function () {
          console.log('fifth readFile success')
        })
      })
    })
  })
})

优势:promise

  • 解决了同步问题(即解决了一个任务时间长时,后面的任务排队,耗时过久,使程序的执行变慢问题)

缺点:服务器

  • 回调地狱(多层级嵌套),会致使逻辑混乱,耦合性高,改动一处就会致使所有变更,嵌套多时,错误处理复杂
  • 不能使用 try...catch 来抓取错误
  • 不能 return
  • 可读性差

Promise

一个 Promise对象表明一个在这个 promise 被建立出来时不必定已知的值。它让您可以把异步操做最终的成功返回值或者失败缘由和相应的处理程序关联起来。 这样使得异步方法能够像同步方法那样返回值:异步方法并不会当即返回最终的值,而是会返回一个 promise,以便在将来某个时候把值交给使用者。

场景网络

let readFile = (path) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (!path) {
        reject('error!!!')
      } else {
        console.log(path + ' readFile success')
        resolve()
      }
    }, 1000)
  })
}

readFile('first')
  .then(() => readFile('second'))
  .then(() => readFile('third'))
  .then(() => readFile('fourth'))
  .then(() => readFile('fifth'))

优势:异步

  • 状态改变后,就不会再变,任什么时候候均可以获得这个结果
  • 能够将异步操做以同步操做的流程表达出来,避免了层层嵌套的回调函数
  • 必定程度上解决了回调地狱的可读性问题

缺点:async

  • 没法取消 promise
  • 当处于 pending 状态时,没法得知目前进展到哪个阶段
  • 代码冗余,有一堆任务时也会使语义不清晰

Generator

Generator函数是 ES6 中提供的一种 异步编程解决方案。语法上,首先能够把它理解成, Generator函数是一个 状态机,封装了多个内部状态,须要使用 next()函数来继续执行下面的代码。

特征

  • function 与函数名之间带有(*)
  • 函数体内部使用 yield 表达式,函数执行遇到 yield 就返回

场景

var readFile = function (name, ms) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log(name + '读完了')
      resolve()
    }, ms)
  })
}

var gen = function* () {
  console.log('指定generator')
  yield readFile('first', 1000)
  yield readFile('second', 2000)
  yield readFile('third', 3000)
  yield readFile('forth', 4000)
  yield readFile('fifth', 5000)
  return '完成了'
}
var g = gen()
var result = g.next()
result.value
  .then(() => {
    g.next()
  })
  .then(() => {
    g.next()
  })
  .then(() => {
    g.next()
  })
  .then(() => {
    g.next()
  })

优势:

  • 能够控制函数的执行,能够配合 co 函数库使用

缺点:

  • 流程管理却不方便(即什么时候执行第一阶段、什么时候执行第二阶段)

async 和 await

async functionsawait 关键字是最近添加到 JavaScript 语言里面的。它们是 ECMAScript 2017 JavaScript 版的一部分(参见 ECMAScript Next support in Mozilla)。简单来讲,它们是基于 promises 的语法糖,使异步代码更易于编写和阅读。经过使用它们,异步代码看起来更像是老式同步代码,所以它们很是值得学习。

image-20210116211018846

场景 1

var readFile = function (name, ms) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log(name + '读完了')
      resolve()
    }, ms)
  })
}

async function useAsyncAwait() {
  await readFile('first', 1000)
  await readFile('second', 2000)
  await readFile('third', 3000)
  await readFile('forth', 4000)
  await readFile('fifth', 5000)
  console.log('async文件阅读完毕')
}
useAsyncAwait()

优势

  • 内置执行器。意味着不须要像 generator 同样调用 next 函数或 co 模块
  • 更广的适用性。async 和 await 后面跟的都是 promise 函数,原始数据类型会被转为 promise
  • 语义更清晰、简洁

    缺点

  • 大量的 await 代码会阻塞(程序并不会等在原地,而是继续事件循环,等到响应后继续往下走)程序运行,每一个 await 都会等待前一个完成

场景 2 场景 1 中的代码,其实 second,third 的伪请求其实并不依赖于 first,second 的结果,但它们必须等待前一个的完成才能继续,而咱们想要的是它们同时进行,因此正确的操做应该是这样的。

async function useAsyncAwait() {
  const first = readFile('first', 1000)
  const second = readFile('second', 2000)
  const third = readFile('third', 3000)
  const forth = readFile('forth', 4000)
  const fifth = readFile('fifth', 5000)
  console.log('async文件阅读完毕')

  await first
  await second
  await third
  await forth
  await fifth
}
useAsyncAwait()

在这里,咱们将三个 promise 对象存储在变量中,这样能够同时启动它们关联的进程。

总结

在这篇文章中,咱们已经介绍了 JavaScript 异步发展史中 --- callback、promise、generator、async/await 的使用方式、优势与缺点。

发展史 优势 缺点
callback 解决了同步问题 回调地狱、可读性差、没法 try / catch 、没法 return
promise 必定程度上解决了回调地狱的可读性 没法取消、任务多时,一样存在语义不清晰
generator 能够控制函数的执行,能够配合 co 函数库使用 流程管理却不方便(即什么时候执行第一阶段、什么时候执行第二阶段
async/await 语义更清晰、简洁,内置执行器 认知不清晰可能会形成大量 await 阻塞(程序并不会等在原地,而是继续事件循环,等到响应后继续往下走)状况

而在现有的异步解决方案中,async/await 是使用人数最多的,它带给咱们最大的好处即同步代码的风格,语义简洁、清晰。

相关文章