回调地狱(callback hell) 和 Promise详细使用

969 阅读5分钟

回调地狱

回调

写了却不调用,给别人调用的函数就是回调 即回头调用一下这个函数

function f1(){}
function f2(fn){
  fn()
}
f2(f1)

上述代码的 f1就是回调

因为我只把它创建出来了,我没有调用它而是f2调用了它

异步和回调的关系

  • 异步任务需要用到回调函数来通知结果
  • 但是回调函数不一定只用在异步任务里
  • 回调可以用到同步里 比如
array.forEach(n=>console.log(n)) // 这个就是同步回调

callback hell 回调地狱

为什么会有回调地狱的出现呢? 我将以下面的例子来解释说明

没有顺序的读取 a b c 三个文件

const fs = require('fs')

fs.readFile('./a.txt', 'utf8', function (err, data) {
    if (err) {
        // return console.log('读取错误');

        // 抛出异常
        // 1.阻止程序的执行  2. 把错误消息打印到控制台  
        throw err
    }
    console.log(data);
})
fs.readFile('./b.txt', 'utf8', function (err, data) {
    if (err) {
        // return console.log('读取错误');

        // 抛出异常
        // 1.阻止程序的执行  2. 把错误消息打印到控制台  
        throw err
    }
    console.log(data);
})
fs.readFile('./c.txt', 'utf8', function (err, data) {
    if (err) {
        // return console.log('读取错误');

        // 抛出异常
        // 1.阻止程序的执行  2. 把错误消息打印到控制台  
        throw err
    }
    console.log(data);
})

这里打印的data是没有顺序的,谁快谁就先执行。

PS F:\nodejs\Node_test> node .\13.回调地狱.js
hello aaaa
hello bbbb
hello cccc
PS F:\nodejs\Node_test> node .\13.回调地狱.js
hello aaaa
hello cccc
hello bbbb
PS F:\nodejs\Node_test> node .\13.回调地狱.js
hello aaaa
hello cccc
hello bbbb

如果想要有顺序的分别 读取 a b c 三个文件

const fs = require('fs')

fs.readFile('./a.txt', 'utf8', function (err, data) {
    if (err) {
        // return console.log('读取错误');

        // 抛出异常
        // 1.阻止程序的执行  2. 把错误消息打印到控制台  
        throw err
    }
    console.log(data);
    fs.readFile('./b.txt', 'utf8', function (err, data) {
        if (err) {
            // return console.log('读取错误');

            // 抛出异常
            // 1.阻止程序的执行  2. 把错误消息打印到控制台  
            throw err
        }
        console.log(data);
        fs.readFile('./c.txt', 'utf8', function (err, data) {
            if (err) {
                // return console.log('读取错误');

                // 抛出异常
                // 1.阻止程序的执行  2. 把错误消息打印到控制台  
                throw err
            }
            console.log(data);
        })
    })

})

我只需一层一层的嵌套即可,等读取a.txt完毕后再去执行b.txt,等读取b.txt完毕后再去执行c.txt

PS F:\nodejs\Node_test> node .\13.回调地狱.js
hello aaaa
hello bbbb
hello cccc
PS F:\nodejs\Node_test> node .\13.回调地狱.js
hello aaaa
hello bbbb
hello cccc
PS F:\nodejs\Node_test> node .\13.回调地狱.js
hello aaaa
hello bbbb
hello cccc

因此想要有顺序的读取异步操作中的文件 就需要回调函数嵌套,但这样的代码 看起来很乱且维护性低

为了解决这样的问题,(回调地狱嵌套) 这时我们就需要用到 promise

Promise

参考文档 promise 阮一峰promise MDN

为什么需要用promise?

我个人理解有三点:

  1. 在使用ajax调用成功和失败方法时,命名不够规范。
  2. 容易出现回调地狱
  3. 很难进行错误处理
  4. 解决异步

promise是什么?

打印出来看看: promise是什么

很显然,promise是一个构造函数,它除了自身有 resolve 、 reject、 all 、race方法等,原型上还有then 、catch 等我们最常用到的方法。 所以我们 new promise 里面是肯定有 then 、 catch 方法的。

如何创建一个promise

function runAsync() {
  const p = new Promise((resolve, reject) => {
    // 异步操作
    setTimeout(() => {
      console.log('执行成功');
      resolve('数据')
    }, 1000)
  })
  return p
}

runAsync()

其中Promise构造函数接受一个参数,就是函数。并且会传入俩个参数:resolve,reject 。 分别表示异步操作执行成功后的回调函数和执行失败后的回调函数

我们将包装一个函数,把promise放在里面并返回它, 这样做的意义在于我们可以使用promise原型上的then,catch方法

runAsync().then(function(data){
    console.log(data);
    //后面可以用传过来的数据做些其他操作
});

效果展示 效果展示

以上就是Promise的作用了,简单的说,就是能把原来的回调写法分离出来,在异步操作执行完后,用链式操作的形式执行回调函数。

promise.all 用法

all方法提供了并行执行异步操作的能力,并且在所有异步操作执行完毕后才执行回调。

请看下面例子:

function runAsync1() {
  const p = new Promise((resolve, reject) => {
    // 异步操作
    setTimeout(() => {
      console.log('执行成功');
      resolve('数据1')
    }, 1000)
  })
  return p
}

function runAsync2() {
  const p = new Promise((resolve, reject) => {
    // 异步操作
    setTimeout(() => {
      console.log('执行成功');
      resolve('数据2')
    }, 1500)
  })
  return p
}

function runAsync3() {
  const p = new Promise((resolve, reject) => {
    // 异步操作
    setTimeout(() => {
      console.log('执行成功');
      resolve('数据3')
    }, 2000)
  })
  return p
}


Promise
  .all([
    runAsync1(),
    runAsync2(),
    runAsync3()
  ])
  .then(result => console.log(result))

我们用Promise.all 来执行, 并且接受三个数组,里面的值都返回的是Promise对象,然后这三个异步操作并行执行,等它们都执行完毕后,数据才会在then方法里面。即all方法会把所有异步操作的结果放在一个属猪中再传给then。

call方法

Promise.race 用法

用法和Promise.all 一样,只不过all方法是谁跑得慢,就以谁为准执行回调, 而race方法就是谁跑得快,就以谁为准执行回调

将上面展示的代码 的all方法换成race看看是什么效果

Promise
  .race([
    runAsync1(),
    runAsync2(),
    runAsync3()
  ])
  .then(result => console.log(result))

 Promise.race

瞧!他会去优先输出最快(1s)的runAsync1函数的数据,

原因是因为异步处理问题,runAsync1函数的数据并不会等待上面的Promise执行完在开始执行,所以由于时间延迟,优先输出‘数据1’。