简单了解如何解决回调地狱

837 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情

我们先了解一下回调地狱是什么,什么是异步代码和Promise的使用流程和原理

回调地狱是什么:

js的异步代码一层层的回调(异步回调,层层嵌套)

例:

image.png

异步代码是什么

我们的js代码主要分为两大类,同步代码和异步代码

同步代码是从上往下立即执行的,能马上看到执行结果,比如console

异步代码会先放在一个任务队列里,等同步代码执行完毕,再来执行异步代码

例如ajax请求、setTimeout()定时器、fs.readFile() 读取文件等都是异步代码

Promise是什么:

Promise 一个 构造函数, 用于创建Promise对象

Promise对象:可以理解为一个处理异步操作的容器

Promise作用:

解决回调地狱

Promise使用流程:

(1)创建promise实例
const p1 = new Promise( (resolve,reject)=>{ //异步代码 } )

(2)调用promise实例的then方法 p1.then( res=>{}).catch( err=>{} )

Promise原理:

(1)promise对象有三种状态 进行中(pending) 已成功(fulfilled) 已失败(rejected)

(2)promise状态只会有两种变化 从pending变为fulfilled 从pending变为rejected

(3)promise对象只要声明, 里面的异步会立即执行。 一般不会在promise内部去处理异步结果,而是调用resolve()或reject()

注意点

(1)promise并不能改变异步的顺序(异步是无序的,编译器决定无法改变), promise本质是控制异步结果的顺序

(2)promise本身只是一个容器,真正异步的是它的两个回调resolve()和reject()

现在我们再来了解Promise是怎么解决回调地狱的:

promise解决回调地狱原理 : 在上一个promise的then方法中, 返回下一个promise实例

fs.readFile() 读取文件是异步代码,我们用它来举个例子

const fs = require('fs')

fs.readFile(`${__dirname}/data/a.txt`, 'utf-8', (err, data) => {

    if (err) {
        throw err
    } else {
        console.log(data)
        fs.readFile(`${__dirname}/data/b.txt`, 'utf-8', (err, data) => {
            if (err) {
                throw err
            } else {
                console.log(data)
                fs.readFile(`${__dirname}/data/c.txt`, 'utf-8', (err, data) => {
                    if (err) {
                        throw err
                    } else {
                        console.log(data)
                        fs.readFile(`${__dirname}/data/d.txt`, 'utf-8', (err, data) => {
                            if (err) {
                                throw err
                            } else {
                                console.log(data)
                            }
                        })
                    }
                })
            }
        })
    }})

我们之前想按顺序读取data文件夹上的a,b,c文件,就要一层层的嵌套,等上一层的同步代码console.log(data)执行结果出来后再往下执行异步代码fs.readFile(),一层层嵌套

我们现在用Promise来解决这一问题

我们可以先封装一个函数

function createPromise(filename) {
return new Promise((resolve, reject) => {
//异步操作
fs.readFile(`${__dirname}/data/${filename}.txt`, "utf8", (err, data) => {
  if (err) {
    //失败
    reject(err)
  } else {
    resolve(data)
  }
})})}

声明调用

 const p1 = createPromise('a')

const p2 = createPromise('b')

const p3 = createPromise('c') 

用Promise的then方法

p1.then(res => {

//开始读取a

  console.log(res)
  
  //返回p2
  
  return p2
  
})

.then(res => {

//p2的then

//开始读取b

console.log(res)

//返回p3

return p3

})

.then(res => {

//p3的then

//开始读取c

console.log(res)

})

可能有人会说那这跟上面的回调地狱有什么区别啊,不还是在嵌套吗?是的,promise虽然解决了异步回调地狱(回调函数层次嵌套)的问题,但是写起来的时候仍然需要嵌套 (链式语法嵌套,需要在上一个promise对象的then方法中返回下一个promise),那我们再了解一下async函数

ES6异步函数async与await

ES2017中引入的更为高级的异步处理机制,async函数,可以让异步的处理变的更加便捷

一句话概括: async函数相当于是promise异步函数的另一种高级写法

async语法如下

  • (1)函数前面使用async修饰

  • (2) 函数内部,promise操作使用await修饰

    • await 后面是promise对象, 左侧的返回值就是这个promise对象的then方法中的结果
    • await必须要写在async修饰的函数中,不能单独使用,否则程序会报错
  • (3)async函数内部的异常需要通过try,catch来捕获

上面的列子我们可以这样写:
const fs = require('fs')
function createPromise(filename) {
    return new Promise((resolve, reject) => {
    //异步操作
    fs.readFile(`${__dirname}/data/${filename}.txt`, "utf8", (err, data) => {
      if (err) {
        //失败
        reject(err)
      } else {
        resolve(data)
      }
    })})}

async function fn(){
    const res1 = await createPromise('a')
    console.log(res1)
    
    const res2 = await createPromise('b')
    console.log(res2)

    //async异步函数的错误信息要用try-catch来捕捉
    try {
        const res3 = await createPromise('c')
        console.log(res3)
    } catch (err) {
        console.log(err)

    }
}
fn()

注意点:

1、async和await异步函数 : 这两个关键字只能用于函数, 所以用的时候一定要放在函数里面用

2、async关键字: 修饰函数。 表示这个函数内部有异步操作。

3、await关键字: 等待异步执行完毕。

4、await只能用于被async修饰的函数中。 只有当await后面的异步操作执行完毕后,才会继续执行后面代码

5、await 后面 只能是promise对象