回调地狱及 Promise 的作用

123 阅读3分钟
  1. 回调地狱

概念:多层回调函数的相互嵌套,就形成了回调地狱

代码如下:

setTimeout(() => { // 第一层回调函数
    console.log( "延迟 1s 钟后执行" )

    setTimeout(() => { // 第二层回调函数
        console.log( "延迟 2s 钟后执行" )

        setTimeout(() => { //第三层回调函数
            console.log( "延迟 3s 钟后执行" )
        }, 3000);
    }, 2000);
}, 1000);

回调地狱的缺点:

  1. 代码耦合性太高,牵一发动全身,难以维护;
  2. 代码冗余,大量代码相互嵌套,难以阅读

1.1 如何解决回调地狱

为了解决回调地狱,ES6 新增了 Promise 的概念

1.2 promise 的基本概念

  1. Promise 是一个构造函数:

    1. 所以它可以被 new 出来:let p = new Promise()
    2. new 出来的 Promise 示例对象 p 代表一个异步操作。
  2. Promise.propotype 上包含一个 .then 方法

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

    1. p.then( 成功的回调函数, 失败的回调函数 )
    2. 写法为:p.then( result =>{} , error=>{} )
    3. 调用 .then() 方法时,成功的回调函数时必选的,失败的回调是可选择的。

1.3 then-fs 的基本使用

调用 then-fs 提供的 readFile()方法可以异步的读取文件内容。他的返回值是 Promise 的实例对象。因此可以调用 .then() 方法为每个成功和失败的回调指定回调函数。

import thenFS from "then-fs"

thenFS.readFile("./1.txt", 'utf8').then((r1) => {console.log(r1)})
thenFS.readFile("./2.txt", 'utf8').then((r2) => {console.log(r2)})
thenFS.readFile("./3.txt", 'utf8').then((r3) => {console.log(r3)})

// 注意:上述代码无法保证文件的读取顺序。为了保证执行顺序,即先执行 1.txt -> 2.txt -> 3.txt 需要把代码按下面的方式写

1.4 .then() 方法的特性

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

//下面代码是按照顺序执行的:1.txt -> 2.txt -> 3.txt

thenFS.readFile("./1.txt", "utf8") // 1. 返回值是 promise 的实例对象
.then( r1 =>{  // 2. 通过 .then 为第一个返回值指定成功的回调函数
    console.log(r1)
    return thenFS.readFile("./2.txt", "utf8") // 3. 在第一个 .then中返回一个新的 Promise实例对象,这样后面可以继续使用 .then
})
.then((r2) => { // 4. 继续调用 .then() 方法,为上一个返回值(Promise 实例对象)指定成功的回调
    console.log(r2);
    return thenFS.readFile("./3.txt", "utf8") // 5. 再返回一个回调函数出来,这样后面就可以用 .then 方法
})
.then((r3) => { // 6. 继续调用 .then() 方法,为上一个返回值(Promise 实例对象)指定成功的回调
    console.log(r3)
})

1.5 .catch() 捕获错误

在 Promise 的链式操作中如果发生了错误,我们可以用 Promise.propertype.catch() 方法捕获错误并进行处理,比如:

thenFS.readFile("./11.txt", "utf8") // 没有 11.txt 这个文件
.then( r1 =>{  
    console.log(r1)
    return thenFS.readFile("./2.txt", "utf8") 
})
.then((r2) => {
    console.log(r2);
    return thenFS.readFile("./3.txt", "utf8") 
})
.then((r3) => {
    console.log(r3)
})
.catch( err => { // 因为没有 11.txt 这个文件,所以错误会被此捕获并输出
    console.log(err)
})

//输出结果为
//[Error: ENOENT: no such file or directory, open 'C:\Users\Administrator\Desktop\es6\11.txt'] {
//  errno: -4058,
//  code: 'ENOENT',
//  syscall: 'open',
//  path: 'C:\\Users\\Administrator\\Desktop\\es6\\11.txt'
//}

如果不希望前面的错误导致后面的代码无法执行,我们可以把 .catch() 的调用提前,如下示例

thenFS.readFile("./11.txt", "utf8") // 没有 11.txt 这个文件
.catch( err => { // 因为没有 11.txt 这个文件,所以错误会被此捕获并输出。后面的代码依旧会执行
    console.log(err)
})
.then( r1 =>{  
    console.log(r1)
    return thenFS.readFile("./2.txt", "utf8") 
})
.then((r2) => {
    console.log(r2);
    return thenFS.readFile("./3.txt", "utf8") 
})
.then((r3) => {
    console.log(r3)
})

// 输出:
//undefined
// 222
// 333