回调地狱问题

  • 回调函数
  • 回调地狱
  • 如何解决回调地狱
    • promise
    • generator
    • async 和 await

一、回调函数

定义:被作为实参传入另一函数,并在该外部函数内被调用,用以来完成某些任务的函数,称为回调函数。

(1)Callback风格的一般约定

确定的一点,任何一个程序流程都会有两种执行结果:正常返回计算出的最终结果、任何一处发生异常抛出错误。

基于这一点,我们用一种通用的方式来约定回调函数应该怎么去写

function callback(error, result) {
  if(error) {
    // 你处理错误的流程
    console.error(error);
  } else {
    // 你处理正确结果的流程
    console.info(result);
  }
}

一般的回调函数为什么是两个参数?这是通过上述的原因约定俗成的结果。其中第一个参数代表子流程运行过程中发生的错误,而第二个参数指的是子流程正常运行的结果。一个接受该回调函数的函数可能长这样:

(2)可以多次调用的回调函数

既然回调函数是作为参数传入函数并在需要的时候调用的,那么并不限定回调函数只能用来处理错误和结果。来看一个多次调用回调函数的例子:

setInterval(function () {
  console.log('Hello');
}, 1000);

前面我们解释过回调函数的定义了,那么很容易可以看出来setInterval这个函数的定义(callback: Function, period: number): void。毋庸置疑,第一个参数其实就是一个回调函数,并且这个回调函数每秒钟会被调用一次。

利用这个方式,我们可以实现一个子流程,输出多次结果。是不是有一点像生成器(Generator)了?

(3)可以不止一个回调函数

回调函数只是参数,那么我们的函数就支持传入多个回调函数。比如:

function foo(handleSucceed, handleFailed) {
  if(isMistake) {
    return handleFailed(new Error('It`s a mistake'));
  } else {
    return handleSucceed('You got it!');
  }
}

foo(function (result) {
  console.log(result);
}, function (error) {
  console.error(error);
});

通过传入两个不同的回调函数,来分别处理成功的结果和错误的结果,是不是感觉代码更加清晰容易理解了呢?你将要了解到的Promise正是利用了这一点。

还有更多的对于回调函数的巧用方式,只要你记住回调函数只是一个函数类型的参数。

二、回调地狱

var fs = require('fs');

fs.readFile('./views/index.html',  (err, data) => {
    if (err) {
        throw err
    }
    console.log(data.toString());
})

fs.readFile('./views/main.html', (err, data) => {
    if (err) {
        throw err
    }
    console.log(data.toString());
})

fs.readFile('./views/update.html', (err, data) => {
    if (err) {
        throw err
    }
    console.log(data.toString());
})

上面的代码中,包括三个异步读取文件的操作,因为是异步操作,文件输出的顺序是不确定的

如果想保证文件输出的顺序,就可以在前一个异步操作的回调函数中调用后一个异步操作,就会出现以下代码:

var fs = require('fs');

fs.readFile('./views/index.html',  (err, data) => {
    if (err) {
        throw err
    }
    fs.readFile('./views/main.html', (err, data) => {
        if (err) {
            throw err
        }
        fs.readFile('./views/update.html', (err, data) => {
            if (err) {
                throw err
            }
            console.log(data.toString());
        })   
        console.log(data.toString());
    })
    console.log(data.toString());
})

这种情况下便出现了回调地狱

假设业务开发中有4个接口,每个接口都依赖于前一个接口的返回,即request2依赖request1,request3依赖request2,这样就容易出现回调地狱的问题

三、如何解决回调地狱

3.1 promise

Promise是ES6标准新增的一个API

new Promise( function(resolve, reject) {...} /* executor */  );

executor

executor是带有 resolve 和 reject 两个参数的函数 。Promise构造函数执行时立即调用executor 函数, resolve 和 reject 两个函数作为参数传递给executor(executor 函数在Promise构造函数返回所建promise实例对象前被调用)。resolve 和 reject 函数被调用时,分别将promise的状态改为fulfilled(完成)或rejected(失败)。executor 内部通常会执行一些异步操作,一旦异步操作执行完毕(可能成功/失败),要么调用resolve函数来将promise状态改成fulfilled,要么调用reject 函数将promise的状态改为rejected。如果在executor函数中抛出一个错误,那么该promise 状态为rejected。executor函数的返回值被忽略。

一般的使用方法如下:

const pms = new Promise((resolve, reject) => {
  // do sth.
  if(isMistake) {
    return resolve(new Error('It`s a mistake'));
  } else {
    return reject('You got it!');
  }
});

pms.then(result => {
  console.log(result);
}).catch(error => {
  console.error(error);
});

有没有发现?和上面一种对回调函数的使用方式出奇的像?

这里的resolvereject正是两个回调函数,就如同前面一个例子里面的handleSucceedhandleFailed一样。而这两个回调函数的传入方式,从上一个例子的直接两个参数传入,变成了通过then方法和catch方法来进行传入

Promisethen方法和catch方法本身也是返回一个Promise对象的,因此可以直接进行链式调用,并且后一次的then方法的回调函数的参数是前一次then方法返回的结果。

实际上Promise上的实例promise是一个对象,不是一个函数。在声明的时候,Promise传递的参数函数会立即执行,因此Promise使用的正确姿势是在其外层再包裹一层函数。

let run = function() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
        let random = Math.random()
        if (random > 0.5) {
            resolve(`resolve:${random}`)
        } else {
            reject(`reject:${random}`)
        }
    }, 1000)
})
}

run()
run().then(
function(value) {
    console.log(value)
})

 

在一个then()方法调用异步处理成功的状态时,你既可以return一个确定的“值”,也可以再次返回一个Promise实例,当返回的是一个确切的值的时候,then会将这个确切的值传入一个默认的Promise实例,并且这个Promise实例会立即置为fulfilled状态,以供接下来的then方法里使用

  let num = 0
    let run = function() {
        return new Promise(resolve => {
            resolve(`${num}`)})
    }

    run().then(val => {
        console.log(val)
        return val
    })
        .then(val =>{
            val++
            console.log(val)
            return val
        })
        .then(val =>{
            val++
            console.log(val)
        })

MDN promise详解

参考资料:https://blog.csdn.net/qq_42911663/article/details/86369813

原文地址:https://www.cnblogs.com/ccv2/p/13223685.html