javascript异步中的回调

同期异步系列文章推荐
谈一谈javascript异步
javascript异步与promise
javascript异步之Promise.all()、Promise.race()、Promise.finally()
javascript异步之Promise.resolve()、Promise.reject()
javascript异步之Promise then和catch
javascript异步之async(一)
javascript异步之async(二)
javascript异步实战
javascript异步总结归档javascript

咱们以前介绍了javascript异步的相关内容,咱们知道javascript以同步,单线程的方式执行主线程代码,将异步内容放入事件队列中,当主线程内容执行完毕就会当即循环事件队列,直到事件队列为空,当用产生用户交互事件(鼠标点击,点击键盘,滚动屏幕等待),会将事件插入事件队列中,而后继续执行。
处理异步逻辑最经常使用的方式是什么?没错这就是咱们今天要说的---回调css

js回调函数

如你所知,函数是对象,因此能够存储在变量中,
因此函数还有如下身份:html

  1. 能够做为函数的参数
  2. 能够在函数中建立
  3. 能够在函数中返回

当一个函数a以一个函数做为参数或者以一个函数做为返回值时,那么函数a就是高阶函数
回调函数
百度百科java

回调函数就是一个经过函数指针调用的函数。若是你把函数的指针(地址)做为参数传递给另外一个函数,当这个指针被用来调用其所指向的函数时,咱们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

维基百科ios

在计算机程序设计中,回调函数,或简称回调(Callback 即call then back 被主函数调用运算后会返回主函数),是指经过函数参数传递到其它代码的,某一块可执行代码的引用。这一设计容许了底层代码调用在高层定义的子程序。

回调函数,几乎天天咱们都在用ajax

setTimeout(() => {
        console.log("这是回调函数");
      }, 1000);
      const hero=['郭靖','黄蓉']
      hero.forEach(item=>{
        console.log(item);
      })

回调函数解决了哪些问题

举一个简单的:axios

let girlName = "裘千尺"

      function hr() {
        girlName = "黄蓉"
        console.log(`我是${girlName}`);
      }

      function gj() {
        console.log(`${girlName}你好,我是郭靖,认识一下吧`);
      }
      hr()
      gj()

输出,重点看输出顺序promise

//=>我是黄蓉
//=>黄蓉你好,我是郭靖,认识一下吧

上面的代码输出是没什么悬念的,不存在异步,都单线程同步执行,最后郭靖和黄蓉相识
若是这时候黄蓉很忙,出现了异步,会怎么样?异步

let girlName = "裘千尺"

      function hr() {
        setTimeout(() => {
          girlName = "黄蓉"
          console.log('我是黄蓉');
        }, 0);
      }

      function gj() {
        console.log(`${girlName}你好,我是郭靖,认识一下吧`);
      }
      hr()
      gj()

输出,重点看输出顺序async

//=>裘千尺你好,我是郭靖,认识一下吧
//=>我是黄蓉

虽然定时器是0ms,可是也致使了郭靖和黄蓉的擦肩而过,这不是咱们指望的结果,hr函数存在异步,只有等主线程的内容走完,才能走异步函数
因此最简单的办法就是使用回调函数解决这种问题,gj函数依赖于hr函数的执行结果,因此咱们把gj做为hr的一个回调函数

let girlName = "裘千尺"

      function hr(callBack) {
        setTimeout(() => {
          girlName = "黄蓉"
          console.log('我是黄蓉');
          callBack()
        }, 0);
      }

      function gj() {
        console.log(`${girlName}你好,我是郭靖,认识一下吧`);
      }
      hr(gj)

输出,重点看输出顺序

//=>我是黄蓉
//=>黄蓉你好,我是郭靖,认识一下吧

⚠️:当回调函数做为参数时,不要带后面的括号!咱们只是传递函数的名称,不是传递函数的执行结果
上面小栗子貌似的很简单,咱们继续

嵌套回调和链式回调

咱们把昨天的demo作一下升级
引入了lodash:处理按钮点击防抖
axios,集成了promis,但promise不是咱们今天讨论的内容,咱们只使用axios的ajax请求接口功能
easy-mock:接口数据,用来实现ajax请求(数据是假的,可是请求是真的)

嵌套回调

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>javascript回调</title>
  <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
  <script src="https://cdn.bootcss.com/lodash.js/4.17.11/lodash.min.js"></script>
</head>

<body>
  <button>点击</button>
  <script>
    {
      const btn = document.querySelector('button')
      btn.onclick = () => {
        _.debounce(() => {
          axios.get('https://easy-mock.com/mock/5b0525349ae34e7a89352191/example/mock')
            .then(data => {
              console.log("ajax返回成功");
              myData = data.data
              console.log(myData);
            })
            .catch(error => {
              console.log("ajax返回失败");
            })
        }, 500)()
      }
    }
  </script>
</body>

</html>

仔细看代码,不难发现,这是一个典型的嵌套回调,咱们分析一下
第一层异步,用户交互,来自按钮的点击事件
第二层异步,按钮去抖,来自lodash下debounce的500ms延时
第三次异步,ajax请求,处理后台接口数据
拿到数据后咱们没有继续作处理,在实际工做中可能还存在异步,还会继续嵌套,会造成一个三角形的缩进区域

再继续嵌套,就会造成所说的“回调地狱”,就是回调的层级太多了,代码维护成本会高不少
上面的栗子最多算是入门毁掉地狱,咱们看一下这个

function funA(callBack) {
        console.log("A");
        setTimeout(() => {
          callBack()
        }, 10);
      }

      function funB() {
        console.log("B");
      }

      function funC(callBack) {
        console.log("C");
        setTimeout(() => {
          callBack()
        }, 100);
      }

      function funD() {
        console.log("D");
      }

      function funE() {
        console.log("E");
      }

      function funF() {
        console.log("F");
      }
//从这里开始执行
      funA(() => {
        funB()
        funC(() => {
          funD()
        })
        funE()
      })
      funF()

(这段代码,带回调的都是异步逻辑)你能很快的看出这段代码的执行顺序吗?
顺序以下:A、F、B、C、E、D
通常正常人不会这么嵌套多层,层级一多,就会考虑拆分

链式回调

const btn = document.querySelector('button')
      //监听按钮点击事件
      btn.onclick = () => {
        debounceFun()
      }
      //去抖动
      const debounceFun = _.debounce(() => {
        ajax()
      }, 500)
      //ajax 请求
      const ajax = function () {
        axios.get('https://easy-mock.com/mock/5b0525349ae34e7a89352191/example/mock')
          .then(data => {
            console.log("ajax返回成功");
            myData = data.data
            console.log(myData);
          })
          .catch(error => {
            console.log("ajax返回失败");
          })
      }

我相信不少人都会经过这种链式回调的方式处理异步回调,由于可读性比嵌套回调要搞,可是维护的成本可能要高不少
上面的栗子,三个异步函数之间只有执行顺序上的关联,并无数据上的关联,可是实际开发中的状况要比这个复杂,

回调函数参数校验

咱们举一个简单的栗子

let girlName = "裘千尺"

      function hr(callBack) {
        setTimeout(() => {
          girlName = "黄蓉"
          console.log('我是黄蓉');
          callBack(girlName)
        }, 0);
      }

      function gj(love) {
        console.log(`${girlName}你好,我是郭靖,认识一下吧,我喜欢${love}`);
      }
      hr(gj)

gj做为hr的回调函数,而且hr将本身的一个变量传递给gj,gj在hr的回调中执行,
仔细看这种写法并不严谨,
若是gj并不仅是一个function类型会怎么样?
若是love的实参并不存在会怎么样?
何况这只是一个简单的栗子
因此回调函数中,参数的校验是颇有必要的,回调函数链拉的越长,校验的条件就会越多,代码量就会越多,随之而来的问题就是可读性和可维护性就会下降。

仍是回调函数的校验

但咱们引用了第三方的插件或库的时候,有时候不免要出现异步回调的状况,一个栗子:
xx支付,当用户发起支付后,咱们将本身的一个回调函数,传递给xx支付,xx支付比较耗时,执行完以后,理论上它会去执行咱们传递给他的回调函数,是的理论上是这样的,咱们把回调的执行权交给了第三方,隐患随之而来
第三方支付,屡次调用咱们的回调函数怎么办?
第三方支付,不调用咱们的回调函数怎么办?
当咱们把回调函数的执行权交给别人时,咱们也要考虑各类场景可能会发生的问题

总结一下:
回调函数简单方便,可是坑也很多,用的时候须要多注意校验

原文连接

相关文章
相关标签/搜索