Promise和async/await

1、回调地狱

多层回调函数的相互嵌套,就形成了回调地狱。示例代码如下:

Promise和async/await_第1张图片

Promise和async/await_第2张图片

回调地狱的缺点: 

  •  代码耦合性太强,牵一发而动全身,难以维护
  • 大量冗余的代码相互嵌套,代码的可读性变差

1.1、如何解决回调地狱的问题

为了解决回调地狱的问题,ES6ECMAScript 2015)中新增了 Promise 的概念。

1.2、Promise 的基本概念

Promise 是一个构造函数

  • 我们可以创建 Promise 的实例 const p = new Promise()
  • new 出来的 Promise 实例对象,代表一个异步操作

Promise.prototype 上包含一个 .then() 方法

  • 每一次 new Promise() 构造函数得到的实例对象,都可以通过原型链的方式访问到 .then() 方法,例如 p.then()

.then() 方法用来预先指定成功和失败的回调函数

  • p.then(成功的回调函数失败的回调函数)
  • p.then(result => { }, error => { })
  • 调用 .then() 方法时,成功的回调函数是必选的、失败的回调函数是可选的

2、基于回调函数按顺序读取文件内容

Promise和async/await_第3张图片

 

3、基于 then-fs 读取文件内容

        由于 node.js 官方提供的 fs 模块仅支持回调函数的方式读取文件,不支持 Promise 的调用方式。因此,需要先运行如下的命令,安装 then-fs 这个第三方包,从而支持我们基于 Promise 的方式读取文件的内容:

npm install then-fs

3.1、then-fs 的基本使用

        调用 then-fs 提供的 readFile() 方法,可以异步地读取文件的内容,它的返回值是 Promise 的实例对象。因此可以调用 .then() 方法为每个 Promise 异步操作指定成功失败之后的回调函数。示例代码如下:

/**
 * 基于 Promise 的方式读取文件
*/
import thenFs from 'then-fs'

// 注意:.then() 中的失败回调是可选的,可以被省略
thenFs.readFile('./files/1.txt', 'utf8').then((r1) => {console.log(r1)})
thenFs.readFile('./files/2.txt', 'utf8').then((r2) => {console.log(r2)})
thenFs.readFile('./files/3.txt', 'utf8').then((r3) => {console.log(r3)})

注意:上述的代码无法保证文件的读取顺序,需要做进一步的改进!

3.2、.then() 方法的特性

        如果上一个 .then() 方法中返回了一个新的 Promise 实例对象,则可以通过下一个 .then() 继续进行处理。通过 .then() 方法的链式调用,就解决了回调地狱的问题。

3.3、基于 Promise 按顺序读取文件的内容

Promise 支持链式调用,从而来解决回调地狱的问题。示例代码如下:

import thenFs from 'then-fs'

thenFs
    .readFile('./files/11.txt', 'utf8')  // 1、返回值是 Promise 的实例对象
    .then((r1) => { // 2、通过 .then 为第一个 Promise 实例指定成功之后的回调函数
        console.log(r1)
        return thenFs.readFile('./files/2.txt', 'utf8')  // 3、在第一个 .then 中返回一个新的 Promise 实例对象
    })
    .then((r2) => { // 4、继续调用 .then 为上一个 .then 的返回值(新的 Promise 实例)指定成功之后的回调函数
        console.log(r2)
        return thenFs.readFile('./files/3.txt', 'utf8') // 5、在第二个 .then 中在返回一个新的Promise实例对象
    })
    .then((r3) => { // 6、继续调用 .then 为上一个 .then 的返回值(新的 Promise 实例)指定成功之后的回调函数
        console.log(r3)
    })

3.4、通过 .catch 捕获错误

  • 在 Promise 的链式操作中如果发生了错误,可以使用 Promise.prototype.catch 方法进行捕获和处理:
  • 如果不希望前面的错误导致后续的 .then 无法正常执行,则可以将 .catch 的调用提前,示例代码如下: 
import thenFs from 'then-fs'

thenFs
    .readFile('./files/11.txt', 'utf8')
    .catch((err) => {       // 捕获第一行发生的错误,并输出错误的消息
        console.log(err.message)    // 由于错误已被及时处理,不影响后续 .then 的正常运行
    })
    .then((r1) => {
        console.log(r1)
        return thenFs.readFile('./files/2.txt', 'utf8')
    })
    .then((r2) => {
        console.log(r2)
        return thenFs.readFile('./files/3.txt', 'utf8')
    })
    .then((r3) => {
        console.log(r3)
    })

3.5、Promise.all() 方法

        Promise.all() 方法会发起并行的 Promise 异步操作,等所有的异步操作全部结束后才会执行下一步的 .then 操作(等待机制)。示例代码如下:

import thenFs from 'then-fs'

// 定义一个数组,存放 3 个读文件的异步操作
const promiseArr = [
  thenFs.readFile('./files/3.txt', 'utf8'),
  thenFs.readFile('./files/2.txt', 'utf8'),
  thenFs.readFile('./files/1.txt', 'utf8'),
]

// 将 Promise 的数组,作为 Promise.all() 的参数
Promise.all(promiseArr).then(result => { // 所有文件读取成功(等待机制)
  console.log(result)
})

注意:数组中 Promise 实例的顺序,就是最终结果的顺序!

3.6、Promise.race() 方法

        Promise.race() 方法会发起并行的 Promise 异步操作,只要任何一个异步操作完成,就立即执行下一步的 .then 操作(赛跑机制)。示例代码如下:

import thenFs from 'then-fs'

// 定义一个数组,存放 3 个读文件的异步操作
const promiseArr = [
  thenFs.readFile('./files/3.txt', 'utf8'),
  thenFs.readFile('./files/2.txt', 'utf8'),
  thenFs.readFile('./files/1.txt', 'utf8'),
]

// 将 Promise 的数组,作为 Promise.race() 的参数
Promise.race(promiseArr).then(result => { // 只要任何一个异步操作完成,就立即执行成功回调函数(赛跑机制)
  console.log(result)
})

4、基于 Promise 封装读文件的方法

方法的封装要求:

  • 方法的名称要定义为 getFile
  • 方法接收一个形参 fpath,表示要读取的文件的路径
  • 方法的返回值Promise 实例对象

4.1、getFile 方法的基本定义

// 1、方法的名称 为 getFile
// 2、方法接收一个形参 fpath,表示要读取的文件路径
function getFile(fpath) {
    // 3、方法的返回值为Promise 的实例对象
    return new Promise()
}

注意:第 5 行代码中的 new Promise() 只是创建了一个形式上的异步操作

4.2、创建具体的异步操作

        如果想要创建具体的异步操作,则需要在 new Promise() 构造函数期间,传递一个 function 函数将具体的异步操作定义到 function 函数内部。示例代码如下:

// 1、方法的名称 为 getFile
// 2、方法接收一个形参 fpath,表示要读取的文件路径
function getFile(fpath) {
    // 3、方法的返回值为Promise 的实例对象
    return new Promise(function() {
        // 下面这行代码,表示这是一个具体的读文件的异步操作
        fs.readFile(fpath, 'utf8', (err, dataStr) => {})
 })
}

4.3、获取 .then 的两个实参

通过 .then() 指定的成功失败的回调函数,可以在 function 形参中进行接收,示例代码如下:

Promise和async/await_第4张图片

4.4、调用 resolve reject 回调函数 

Promise 异步操作的结果,可以调用 resolve reject 回调函数进行处理。示例代码如下:

import fs from 'fs'

function getFile(fpath) {
  // resolve是“成功的”回调函数,reject是“失败的”回调函数
  return new Promise(function (resolve, reject) {
    fs.readFile(fpath, 'utf8', (err, dataStr) => {
      if (err) return reject(err) // 如果读取失败,则调用“失败的回调函数”
      resolve(dataStr)        // 如果读取成功,则调用“成功的回调函数”
    })
  })
}

getFile('./files/11.txt')
  .then((r1) => {
    console.log(r1)
  }) // then方法中,错误的回调函数可以省略,我们可以使用catch方法来捕获异常
  .catch((err) => console.log(err.message))

5、什么是 async/await

        async/await ES8ECMAScript 2017)引入的新语法,用来简化 Promise 异步操作。在 async/await 出现之前,开发者只能通过链式 .then() 的方式处理 Promise 异步操作。示例代码如下:

import thenFs from 'then-fs'

thenFs
    .readFile('./files/11.txt', 'utf8')
    .catch((err) => {
        console.log(err.message)
    })
    .then((r1) => {
        console.log(r1)
        return thenFs.readFile('./files/2.txt', 'utf8')
    })
    .then((r2) => {
        console.log(r2)
        return thenFs.readFile('./files/3.txt', 'utf8')
    })
    .then((r3) => {
        console.log(r3)
    })
  • .then 链式调用的优点:解决了回调地狱的问题
  • .then 链式调用的缺点:代码冗余、阅读性差、不易理解

6、async/await 的基本使用

使用 async/await 简化 Promise 异步操作的示例代码如下:

import thenFs from 'then-fs'


// 按照顺序读取文件 1,2,3
async function getAllFile() {
    const r1 = await thenFs.readFile('./files/1.txt', 'utf8')
    console.log(r1)
    const r2 = await thenFs.readFile('./files/2.txt', 'utf8')
    console.log(r2)
    const r3 = await thenFs.readFile('./files/3.txt', 'utf8')
    console.log(r3)
}

getAllFile()

async/await 使用注意事项

  1. 如果在 function 中使用了 await,则 function 必须被 async 修饰
  2. 在 async 方法中,第一个 await 之前的代码会同步执行,await 之后的代码会异步执行
import thenFs from 'then-fs'

console.log('A')

async function getAllFile() {
    console.log('B')
    const r1 = await thenFs.readFile('./files/1.txt', 'utf8')
    console.log(r1)
    const r2 = await thenFs.readFile('./files/2.txt', 'utf8')
    console.log(r2)
    const r3 = await thenFs.readFile('./files/3.txt', 'utf8')
    console.log(r3)
    console.log('D')
}

getAllFile()
console.log('C')

Promise和async/await_第5张图片

你可能感兴趣的:(JavaScript,node,前端,javascript,es8,node.js,ecmascript)