• 回调地狱问题


    • 回调函数
    • 回调地狱
    • 如何解决回调地狱
      • 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

  • 相关阅读:
    12.SolrCloud原理
    11.SolrCloud集群环境搭建
    10.Solr4.10.3数据导入(DIH全量增量同步Mysql数据)
    9.Solr4.10.3数据导入(post.jar方式和curl方式)
    Java程序设计之最大公约数和最小公倍数
    Java程序设计之正则表达式
    Java程序设计之整数分解
    Java程序设计之裴波拉切那数列(兔子一年的数量)
    Java并发编程实例(synchronized)
    Java程序设计之合租房synchronized(二)
  • 原文地址:https://www.cnblogs.com/ccv2/p/13223685.html
Copyright © 2020-2023  润新知