什么是回调地狱,如何用Promise解决回调地狱,看完这篇你就明白了。

语言: CN / TW / HK

前言

回调地狱,其实简单来说就是回调函数的嵌套。我们先了解一点什么是回调函数和异步任务。

1.回调函数:

把一个函数当作参数传递,传递的是函数的定义并不会立即执行,而是在将来特定的时机再去调用,这个函数就叫做回调函数。在定时器setTimeout以及Ajax的请求时都会用到回调函数。

setTimeout(function(){ //这个function()就是回调函数,它只有在1秒后才会执行 console.log('执行了回调函数'); },1000)

2.异步任务:

与之相对应的概念是“同步任务”,同步任务在主线程上排队执行,只有前一个任务执行完毕,才能执行下一个任务。异步任务是那些被引擎放在一边,不进入主线程、而进入任务队列的任务。前一个任务是否执行完毕不影响下一个任务的执行。

01.jpg

一.什么是回调地狱(Callback Hell)

存在异步任务的代码,不能保证能按照顺序执行,如果我们需要代码顺序执行,要怎么写呢?

setTimeout(function () { //第一层 console.log(111); setTimeout(function () { //第二程 console.log(222); setTimeout(function () { //第三层 console.log(333); }, 1000) }, 2000) }, 3000) 这种回调函数的层层嵌套,就叫做回调地狱。回调地狱会造成代码可复用性不强,可阅读性差,可维护性(迭代性差),扩展性差等等问题。

那该如何解决回调地狱呢?

二.解决方法

2.1 Promise语法

Promise,中文翻译过来就是'承诺',意思是在未来某一个时间点承诺返回数据给你。它是js中的一个原生对象,是一种异步编程的解决方案,可以替换掉传统的回调函数解决方案。

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

promise本身只是一个容器,真正异步的是它的两个回调resolve()和reject() 1. promise对象有三个状态:pending(进行中),fulfilled(已成功),rejected(已失败) 1. Promise对象的状态改变, 只有两种可能:

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

b. 从pending变为rejected。
    * 此时应该执行 reject();
  1. promise在创建对象的时候,里面的代码会立即执行.

    a. promise创建时,里面的代码还是异步无序操作

    b. promise的原理是,利用then方法将异步操作的结果,按照顺序执行,catch方法用来接收处理失败时相应的数据

    总结: 不要在创建promise的时候去处理异步操作结果,而应该通过 then() 方法来处理

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

在上一个promise的then方法中,返回下一个promise

`` //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.

//开始读取a getPromise('a').then((data)=>{ console.log(data); //继续读取b return getPromise('b'); }).then((data)=>{ console.log(data); //继续读取c return getPromise('c'); }).then((data)=>{ console.log(data); }).catch((err)=>{ //以上三个异步操作,只要有任何一个出错,都会执行err console.log(err); }); ```

  • 但是如果过多的使用then 也会照成新的执行流程问题。所以ES7推出了async/await (异步等待)

2.2 async/await

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

  • async语法如下

  • 函数前面使用async修饰

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

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

02.jpg

  1. async函数内部的异常需要通过try,catch来捕获

``` const fs = require("fs");

/ promise实例对象的catch方法 : 用于捕获异步操作的错误信息 /

//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.

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

/ async关键字: 修饰函数。 表示这个函数内部有异步操作。 await关键字: 等待异步执行完毕。 (1)await只能用于被async修饰的函数中。
只有当await后面的异步操作执行完毕后,才会继续执行后面代码 (2)await 后面 只能是promise对象
/

const readFile = async () => {

let data1 = await getPromise('a')
console.log(data1)

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

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

}

}

readFile() ```

三.总结

当我们写代码遇到异步回调时,我们想让异步代码按照我们想要的顺序执行,如果按照传统的嵌套方式,就会出现回调地狱,这样的代码不利于维护。我们可以通过Promise对象进行链式编程来解决,这样尽管可以解决问题,但是ES7给我们提供了更加舒适的async/await语法糖。

希望以上文章能够给读者一些帮助。