zoukankan      html  css  js  c++  java
  • 回调地狱问题

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

  • 相关阅读:
    PythonStudy——数据类型总结 Data type summary
    PythonStudy——可变与不可变 Variable and immutable
    PythonStudy——列表操作 List operatio
    PythonStudy——列表的常用操作 List of common operations
    PythonStudy——列表类型 List type
    PythonStudy——字符串扩展方法 String extension method
    PythonStudy——字符串重要方法 String important method
    AWT,Swing,RCP 开发
    JQuery插件机制
    最新知识网站
  • 原文地址:https://www.cnblogs.com/ccv2/p/13223685.html
Copyright © 2011-2022 走看看