如何解决回调地狱?教你简单粗暴解决问题

3,039 阅读4分钟

什么是回调地狱?

    网上很多关于回调地狱的图片,听起来很深奥,其实简单来说就是异步回调函数的层层嵌套。

    众所周知,函数分为同步和异步两种,其中异步函数是无序执行的,而我们在实际开发经常会遇到一些需求,要求我们需要有序的执行这些异步函数。这个时候就需要使用异步函数的层层嵌套来让其按照我们需要的顺序有序的执行。当异步回调函数一层一层的嵌套之后,就会形成回调地狱,代码冗余复杂。


案例1:依次读取 文件 a.txt , b.txt , c.txt 这三个文件内容

const fs = require('fs');

//(1)能直接按照顺序写吗?  : 不能,因为异步操作 是无序的
fs.readFile("./data/a.txt", 'utf-8', (err, data) => {
    if(err){
        console.log(err);
    }else{
        console.log(data);
    };
});

fs.readFile("./data/b.txt", 'utf-8', (err, data) => {
    if(err){
        console.log(err);
    }else{
        console.log(data);
    };
});

fs.readFile("./data/c.txt", 'utf-8', (err, data) => {
    if(err){
        console.log(err);
    }else{
        console.log(data);
    };
});


//(2)解决方案 : 在回调函数中嵌套执行
//弊端 : 形成回调地狱(异步回调 `层层嵌套`,非常麻烦且不便于维护)
//读取文件a.txt
fs.readFile("./data/a.txt", 'utf-8', (err, data) => {
    if(err){
        console.log(err);
    }else{
        console.log(data);
        //a.txt读取成功之后开始读取b.txt        fs.readFile("./data/b.txt", 'utf-8', (err, data) => {
            if(err){
                console.log(err);
            }else{
                console.log(data);
                //b.txt读取成功之后开始读取 c.txt                 fs.readFile("./data/c.txt", 'utf-8', (err, data) => {
                    if(err){
                        console.log(err);
                    }else{
                        console.log(data);
                    }
                });
            }
        });
    }
});

以上就是回调地狱的形成过程,那么我们改如何解决呢?

Promise语法

1.Promise是什么?

     Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES7 将其写进了语言标准,统一了用法,原生提供了Promise对象。

总结一下

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

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

  • Promise作用:解决回调地狱


1.promise对象有三个状态. 

 a. pending(进行中) 

 b. fulfilled(已成功) 

 c. rejected(已失败) 


2.Promise对象的状态改变, 只有两种可能:

 a. 从pending变为fulfilled 。此时应该执行 resolve(); 

 b. 从pending变为rejected。此时应该执行 reject();

总结:promise本质不是控制 异步代码的执行顺序(无法控制) , 而是控制异步代码结果处理的顺序

2.Promise如何使用?

使用流程 

 (1)实例化Promise对象 : 将异步操作放入Promise中 

  • resolve:异步操作 成功状态
  • reject : 异步操作 失败状态

 (2)调用then() 方法: 处理异步操作结果\

  • promise对象.then((data)=>{ 处理成功数据 },(err)=>{ 处理失败信息 });

案例2:Promise语法依次读取文件

const fs = require('fs');

//(1) 创建三个异步操作  promise

//读取文件A
const p1 = new Promise((resolve,reject)=>{
    //读文件
    fs.readFile('./data/a.txt','utf8',(err,data)=>{
       if(err == null){ 
            //成功
           resolve(data);
       }else {
           //失败
           reject(err); 
       }
    });
});

//读取文件B
const p2 = new Promise((resolve,reject)=>{
    //读文件
    fs.readFile('./data/b.txt','utf8',(err,data)=>{
       if(err == null){ 
            //成功
           resolve(data);
       }else {
           //失败
           reject(err); 
       }
    });
});

//读取文件C
const p3 = new Promise((resolve,reject)=>{
    //读文件
    fs.readFile('./data/c.txt','utf8',(err,data)=>{
       if(err == null){ 
            //成功
           resolve(data);
       }else {
           //失败
           reject(err); 
       }
    });
});

// (2)按照顺序处理异步操作结果
p1.then((data)=>{                  //第一个异步结果
    console.log(data);
    return p2;                  //返回下一个promise
}).then((data)=>{              // 第二个异步结果  
    console.log(data);
    return p3;                 //返回下一个promise
}).then((data)=>{              // 第三个异步结果  
    console.log(data);
});

promise解决回调地狱原理 : 在then方法中返回一个promise对象

(链式语法嵌套,需要在上一个promise对象的then方法中返回下一个promise)

promise虽然解决了异步回调地狱(回调函数层次嵌套)的问题,但是写起来的时候仍然需要嵌套。

所以有一种更简单的方法:异步函数async与await

ES6异步函数async与await

async函数是ES2017中引入的更为高级的异步处理机制,可以让异步的处理变的更加便捷,相当于是promise语法的 “高级写法”。

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

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

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

 

async语法很简单

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

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

注意点:

  • await 后面是promise对象, 左侧的返回值就是这个promise对象的then方法中的结果
  • await必须要写在async修饰的函数中,不能单独使用,否则程序会报错

案例3:使用async异步函数依次读取文件

const fs = require("fs");

//1.封装一个函数 :  根据文件名生成  文件读取的promise
function getPromise(fileName) {
    let p = new Promise((resolve, reject) => {
        //读文件
        fs.readFile(`./data/${fileName}.txt`, 'utf-8', (err, data) => {
            if (err == null) {
                //成功
                resolve(data);
            } else {
                //失败
                reject(err);
            }
        });
    });
    return p;
};

//2.解决需求: 要先读a, 读完a后读b, 读完b后读c.
const readFile = async function() {
    
    let data1 = await getPromise('a')
    console.log(data1)

    let data2 = await getPromise('b')
    console.log(data2)

    let data3 = await getPromise('c')
    console.log(data3)


readFile()



目前这种方法比promise更加高效快捷,跟推荐大家使用异步函数async来解决回调地狱的问题。

感谢大家的观看,有不足的地方可以提出来,大家一起探讨,希望能够跟大家一起共同提高进步!!