Promise

回调地狱

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

    回调地狱更多导致的问题还是:难以阅读和维护,通常出现在异步操作中
    其和递归问题导致的栈溢出还是不同的,两者是不同的问题

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    setTimeout( () =>{ //第1层回调函数
    console.log( '延时1秒后输出')

    setTimeout(() => { //第2层回调函数
    console.log('再延时2秒后输出')

    setTimeout(() => { //第3层回调函数
    console.log('再延时3秒后输出')
    },3000)

    },2000)

    },1000)

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

  2. 如何解决
    为了解决回调地狱的问题,ES6 (ECMAScript 2015)中新增了Promise的概念。

    解决回调地狱的方法包括使用 Promise、async/await 等异步编程技术,以及使用函数式编程的方式来组织代码,使其更具可读性和可维护性。

Promise基本概念

  1. Promise是一个构造函数

    • 我们可以创建Promise 的实例 const p = newPromise()
    • new出来的Promise 实例对象,代表一个异步操作
  2. Promise.prototype 上包含一个.then()方法

    • 每一次new Promise()构造函数得到的实例对象,
    • 都可以通过原型链的方式访问到.then()方法,例如p.then()
  3. .then()方法用来预先指定成功和失败的回调函数

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

基于 then-fs 异步的读取文件内容

  1. 基于回调函数按顺序读取文件内容
    虽然可以保证文件的读取顺序,但是形成了一个回调地狱的问题
    img

  2. 基于 then-fs 读取文件内容
    fs模块仅支持 以回调函数的方式 读取文件,不支持Promise的调用方式。因此,需要先安装 then-fs 第三方包,从而支持基于Promise的方式读取文件的内容: npm install then-fs

  • then-fs 的基本使用
    调用 then-fs 提供的readFile()方法,可以异步地读取文件的内容,它的返回值是 Promise 的实例对象。因此可以调用.then()方法为每个 Promise 异步操作指定成功失败之后的回调函数。示例代码如下:
    1
    2
    3
    4
    5
    6
    // 基于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);})
    当前只是异步的去读文件,无法保证文件的读取顺序,多次执行读取顺序不一

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

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

  • 基于Promise按顺序读取文件的内容
    只要任何方法返回值是一个Promise实例对象,那就都可以继续调用.then()方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    import thenFs from 'then-fs'

    thenFs.readFile('./files/1.txt', 'utf8')// 返回值是Promise的实例对象
    .then((r1) => { // 通过.then 为返回的promise实例指定成功之后的回调函数
    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)
    })
  • 通过 .catch 捕获错误
    在 Promise 的链式操作中如果发生了错误,可以使用Promise.prototype.catch方法进行捕获和处理:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    import thenFs from 'then-fs'

    thenFs.readFile('./files/11.txt', 'utf8') // 文件不存在
    .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)
    })
    .catch((err) => {
    console.log(err.message)
    }) // 只返回错误,没有读取的文件内容
  • 如果不希望前面的错误导致后续的.then无法正常执行,可以将.catch的调用提前

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    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)
    }) // 先打印错误,然后打印读取内容,r1为undefined,r2r3读取成功

Promise.all()方法

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import thenFs from 'then-fs'

const promiseArr = [
thenFs.readFile('./files/1.txt', 'utf8'),
thenFs.readFile('./files/2.txt', 'utf8'),
thenFs.readFile('./files/3.txt', 'utf8')
]

Promise.all(promiseArr).then(result =>{
console.log(result); // [ '111', '222', '333' ]
})
.catch(err=>{
console.log(err.message);
})

Promise示例的顺序,就是最终拿到的结果的顺序。如果有一个文件读取错误,所有数据都拿不到

Promise.race() 方法

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import thenFs from 'then-fs'

const promiseArr = [
thenFs.readFile('./files/1.txt', 'utf8'),
thenFs.readFile('./files/2.txt', 'utf8'),
thenFs.readFile('./files/3.txt', 'utf8')
]

Promise.race(promiseArr).then(result =>{
console.log(result);
})
.catch(err=>{
console.log(err.message);
})

只会拿到一个文件的读取结果,读取最快的那个,无法保证读取的文件是哪个

基于Promise封装异步读文件的方法

  • 方法的封装要求:

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

    1
    2
    3
    function getFile(fpath){
    return new Promise()
    }

    注意:new Promise() 只是创建了一个形式上的异步操作

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

    1
    2
    3
    4
    5
    6
    7
    import fs from 'fs'

    function getFile(fpath){
    return new Promise(function(){
    fs.readFile(fpath,'utf-8',(err,dataStr)=>{ })
    })
    }
  • 获取.then的两个实参
    通过.then()指定的成功和失败的回调函数,可以在 function的形参中 进行接收,示例代码如下:
    img

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    import fs from 'fs'

    function getFile(fpath) {
    return new Promise(function (resolve,reject) {
    fs.readFile(fpath, 'utf-8', (err, dataStr) => {
    if(err) return reject(err) // 发生错误
    resolve(dataStr) // 读取成功
    })
    })
    }

    getFile('./files/1.txt').then((r1)=>{console.log(r1)},(err)=>{console.log(err.message);})
    // 失败的回调函数可以省略不写,可以用catch进行捕获
    getFile('./files/1.txt').then((r1)=>{console.log(r1)}).catch((err)=>{console.log(err.message)})