js多回调函数

Posted MirrorSpace

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了js多回调函数相关的知识,希望对你有一定的参考价值。

多回调问题

前端编程时,大多通过接口交换数据,接口调用都是异步的,处理数据都是在回调函数里.

假如需要为一个用户建立档案,需要准备以下数据,然后调用建档接口

name     // 用户名字 使用接口 get_name(userid)

files       // 用户附件  使用接口 get_atta(userid,name,location)

create_record(userid,name,location) // 调用建档接口

create_files(recordId,userid,files)   // 上传用户文件

要解决这个问题,可以先调用name接口,在其回调里调用addr接口,然后在addr的回调里调用create_record.....

这难以想象,代码会写得很长,回调嵌套多层.虽然能解决问题,但是,程序代码顺序执行的思维,很难理清这种问题,如果回调再多一些,代码就难以维护.一看就头晕.

也许同步是入门异步是进阶,前者看,后者思

同步方法

依然使用程序顺序执行的思路,将代码顺序写出,(假如接口是同步得到结果的)

function createRecord(userid){
  var
name = get_name(userid);   var files = get_atta(userid,name,location);   var recordId = create_record(userid,name,location);   create_files(recordId,userid,files);
}

异步方法时,不会得到正确结果,但如果设法让上述代码顺序执行,执行完第1个方法再执行第2个...,也就是同步的思路.那么就能得到正确结果.

顺序等待

等待前面的方法回调后,再执行后面的方法.

  let user = { userid: 1 } // 保存参数
  let isOk = false;// 全部成功时,设为true
  //
function createRecord() {
   // 获取名字
if (!user.hasOwnProperty(‘name‘)) {
    // 异步方法 参数1:数据 2:成功时执行 3:失败时执行 get_name(user, (res)
=> {
user.name
= res.Name;
      console.log(‘获取名字成功‘);
      // 成功得到结果后,递归调用.由于if条件,递归时不会重复执行 createRecord(); }, (err)
=> { // 不成功时,方法结束 });
    // 保证异步成功返回时,才执行后续方法
return; }
// 获取文件数据
if (!user.hasOwnProperty(‘files‘)) { get_atta(user, (res) => { user.files = res.Attas;
      console.log(‘获取文件成功‘) createRecord(); }, (err)
=> { // }); return; } // 建档接口,返回ID if (!user.hasOwnProperty(‘recordId‘)) { .....return; } // 调用文档接口,有文件时才上传 if (user.files.length > 0) { create_files(user, (res) => {
      // 最后一个接口,当成功返回时,表示全部请求成功了
      console.log(‘上传文件成功‘) isOk
= true; }, (err) => { // }); } else { isOk = true; } }

每次递归时,只会执行其中一个IF块,IF块就是接口调用,当成功返回时,刷新IF条件,继续递归,失败时不递归.

如此,模拟了"顺序执行".在思路上比较清晰,和同步方法顺序执行的思路一至.

此法缺点多,首先此法是同步接口方法的在形式上的生搬硬套.将所有请求接口的方法按顺序写在一个方法内,使用IF条件判断是否执行,使用递归反复执行.

IF块代码较长时,较多时,整个方法就很长,难以维护.方法作用旨在"控制每个接口方法的顺序执行",只是个代码的容器.

条件逻辑复杂,如果判定条件写错了,或者忘了刷新判定条件,很容易造成无限递归,程序崩溃.

顺序调用

将IF块写成独立方法,在其中调用下一个步骤的方法,直到调用最后一个方法.

  let user = { userid: 1 } // 保存参数
  let isOk = false;// 全部成功时,设为true
  // 获取名字
  function getName(user)
  {
    get_name(user, (res) =>
    {
      user.name = res.Name;
      // 成功后,调用获取文档方法
      getAtta(user);
    });
  }
  // 获取文档
  function getAtta(user)
  {
    get_atta(user, (res) =>
    {
      user.files = res.Attas;
      // 成功后,调用建档方法
      createRecord(user);
    })
  }
  // 建立档案
  function createRecord(user)
  {
    create_record(user, (res) =>
    {
      user.recordId = res.RecordId;
      // 成功后,调用上传文件方法
      createFiles(user);
    })
  }
  // 上传文档方法
  function createFiles(user)
  {
    if (user.files.length == 0)
    {
      isOk = true;
      return;
    }
    create_files(user, (res) =>
    {
      isOk = true;
    })
  }
  // 调用
  getName(user);

这个办法与递归法相比,不将所有方法写到一个方法内,反而拆分为独立方法,每个方法再调用下个方法.最后完成所有请求.

没有条件语句逻辑更清晰明了,各接口方法完成各自取数据任务,有一个起始方法,和最后一个结束方法,链环式的.按顺序一个接一个调用的.如果其中一个出错,那么终止.

各方法独立,依然按照递归法的顺序调用后续方法,相比所有方法写在一起的递归法,只是在形式上分开了,也许好维护些,但缺少"封装"性.

容器方法

结合前两种办法的特点,造一个类管理方法的执行,返回值以及出错信息.

    function moreAjax()
    {
      let self = this;
      // 全部成功时为true
      self.AllOk = false;

      //  方法容器
      let ajaxList = [];

      // 每个方法成功时返回值容器
      self.ResList = [];

      // 每个方法错误时返回值容器
      self.ErrList = [];

      // 方法执行序列
      let index = 0;

      // 添加方法
      self.add = function (method)
      {
        ajaxList.push(method);
      }

      // 开按执行 按add时顺序执行
      self.start = function ()
      {
        if (index == ajaxList.length)
        {
          // 全部成功标识
          self.AllOk = true;
          return;
        }
        //
        ajaxList[index](
          (res) =>
          {
            console.log(‘(success)执行序号‘ + index);
            // 保存回调结果
            self.ResList.push(res);
            // 递归调用,执行下个方法
            index = index + 1;
            self.start();
          },
          (err) =>
          {
            console.log(‘(error)执行序号‘ + index);
            // 保存出错结果
            self.ErrList.push(err);
          });
      }
    }

    // 调用
    let test = new moreAjax();
    // 添加方法
    test.add((success, error) =>{
         get_name(
             (res)=>{success(res)},
             (err)=>{success(err)}
         )
    })
    // 再添加方法
    test.add((success, error) =>{
         get_location(
             (res)=>{success(res)},
             (err)=>{success(err)}
         )
    })
    // 添加最后一个方法
    test.add((success, error) =>{
         success("lastMethod");
         if (send.AllOk)
         {
             console.log(send.ResList);
         } else
         {
             console.log(send.ErrList);
         }
    })
    // 启动
    test.start();

容器法是前两种办法的优化,兼顾"封装"性和灵活性.

add方法添加接口方法,方法要求前两个参数第1个用于成功时执行,第2个用于失败时执行. 每调用一次add,则向ajaxList加入一个方法.

start方法执行后,方法会按add的顺序开始执行,每个方法执行完成时,若成功会记录返回值,若失败记录失败信息.通过ResList和ErrList数组属性,访问这些值.

如果每个方法都正确返回,那么会执行到ajaxList里的最后一个方法,如果其中一个失败,则会停止.

每个方法成功时,ResList增加一个结果.第一个方法成功时,结果值为 ResList[0] 之后的方法通过此属性可访问之前方法返回的结果.

 

以上是关于js多回调函数的主要内容,如果未能解决你的问题,请参考以下文章

几个关于js数组方法reduce的经典片段

Async.js解决Node.js操作MySQL的回调大坑

关于js的callback回调函数的理解

Node.js回调函数

js 回调函数

js异步请求发展史和yield