资讯专栏INFORMATION COLUMN

小程序异步问题:多个网络请求依次执行并依次收集请求结果

Elle / 2517人阅读

摘要:异步带来的问题小程序的网络请求是异步的我们无法通过来将网络请求结果返回出来使用。省略其他属性接业务逻辑代码例如这个微信的网络请求,我们可以通过和的回调函数来读取的值从而完成依赖结果的业务逻辑。

业务逻辑

最近开发一个便签小程序的时候,有这样一个需求:用户可以在写便签的时候添加一个或多个图片。

对于这个需求,我们用户按下保存键时,内部具体的实现上是这样的逻辑:

首先检测用户是否传入了图片,如果存储本地图片地址的数组长度>=1,则将图片数组放入上传图片的函数。

由于小程序网络请求大小限制,我们只能采取循环上传单文件,然后收集每次请求的结果--图片在服务器的地址,最后将结果放在一个数组中供后续的操作使用。

当图片上传函数全部执行完毕后,将数组中的图片数组取出来,赋值到日记对象中,再将整个日记对象提交到服务器。

服务器返回保存成功或失败。

思路其实非常清晰简单,但是在代码实现上却翻了大跟头。

异步带来的问题

小程序的网络请求是异步的:我们无法通过return来将网络请求结果返回出来使用。

    wx.request({

          //...省略其他属性

          success: function (res) {

          },

          fail: function (res) {

          }

    })

例如在微信中发送网络请求,我们只能使用微信提供的方法wx.xxx,其中请求的结果保存在res中,而res无法直接return得到。

解决:res虽然无法直接获取,但是我们能通过将需要使用到这个请求结果的业务逻辑代码放入这个网络请求的回调函数中直接读取网络请求结果,也就是一切都需要通过回调来解决。

    wx.request({

          //...省略其他属性

          success: function (res) {

            console.log(res);

            //接业务逻辑代码

          },

          fail: function (res) {

            console.log(res);

          }

    })

例如这个微信的网络请求,我们可以通过success和fail的回调函数来读取res的值从而完成依赖res结果的业务逻辑。

回调地狱

虽然解决了结果获取的问题,但是又产生了另一个问题,当多个请求中有明确的先后顺序时,回调会嵌套的很厉害,造成回调地狱,代码可读性和可维护性都会很差。
例如对于一个日记页面,需要先请求到页面的数据(里面包含了图片数据和其他数据的地址),再根据页面数据去请求图片数据后再请求音频数据。例如以下代码:

   //请求页面整体数据

   wx.request({

         //...省略其他属性

         success: function (res) {//成功

               //请求图片数据

               wx.request({

                 success: function (res) {//成功

                     //请求音频数据

                     wx.request({

                         success: function (res) {//成功

                         },

                         fail: function (res) {//失败

                             console.log("请求失败:"+res);

                         }

                     })

                 },

                 fail: function (res) {//失败

                     console.log("请求失败:"+res);

                 }

               })

         },

         fail: function (res) {//失败

             console.log("请求失败:"+res);

         }

   })

如何优化?幸运的是,在es6里面我们可以用promise去优化我们的回调,用then代替回调,首先将网络请求封装成一个Promise:

   // 后台post请求

   function postRequest(posturl, postdata) {

     return new Promise((resolve, reject) => {

       wx.request({

         //省略其他属性

         success: function (res) {

           console.log("at post request: 请求成功")

           resolve(res.data)//设置promise成功标志

         },

         fail: function (res) {

           console.log("at post request: 请求失败")

           reject(res.data)//设置promise失败标志

         }

       })

     });

   }

这样封装以后,我们的网络请求会在success和fail后回调resolve,这样可以告诉promise,“hey,我完成我的工作了,你可以进行你的then操作了”,这样就可以用then来简化嵌套逻辑。使用promise来完成上面那个问题的请求将会是这样的:

    postRequest(posturl,postdata)

    .then(function(res){

      //业务逻辑

      //调用下一个请求

      return postRequest(next_posturl,next_postdata);

    })

    .then(function(res){

      //业务逻辑

      //调用下一个请求

      return postRequest(next_next_posturl,next_next_postdata);

    })

    .then(function(res){

      //业务逻辑

    });

是不是简洁的多~

一个看似简单的需求

我们的有一个很简单的需求是需要对一组数量不定的图片做分别上传(因为微信限制所以无法做多上传),并且在上传完成以后需要获取到所有的返回结果。

那么用我们前面的回调函数+then的话,很自然的想到这样的写法

postRequest(posturl,postdata)

.then(function(res){

    //获取返回res

    //上传下一个图片

    return postRequest(next_posturl,next_postdata);

})

.then(function(res){

    //获取返回res

    //上传下一个图片

    return postRequest(next_next_posturl,next_next_postdata);

})

.then(function(res){

    //获取返回res

});

这样看起来很简单明了,但是我的图片数量是不定的,怎么动态的构建.then.then.then这样的链式调用呢?经过我的研究后发现可以通过一个辅助的promise链去完成主链的链式构建。

//多文件上传

function jabingp_upLoad(uploadurl, files) {

  return new Promise((resolve, reject) => {

    //初始化promise链

    var mergedAjax = Promise.resolve();

    var response = [];

    // 循环上传

   
    // 这里一定要使用let来为没一次循环构建一个块级作用域
    // 使用var则需要配合立即执行函数
    for (let i = 0; i < files.length; i++) {

      mergedAjax = mergedAjax.then(() => {

        return jabingp_upLoadSingle(uploadurl, files[i]).then((res) => {

          response.push(res);

        });

      });

    }

    //当前面循环中所有的then执行完毕时会执行这个then

    mergedAjax.then(() => {

      resolve(response);            //设置这个函数的promise对象为完成状态并放入数据

    });

  });

}

通过这个函数,就完成了多个请求依次执行并收集结果的效果。这个函数的重点在于利用另外一个已经处于完成状态的promise,不断的迭代自身,在每次迭代的then内部通过return来完成辅助链到业务链的切换。

2019-04-27 更新
使用await/async更加优雅地处理异步吧!

在es7标准中,引入了await和async这对兄弟,它们可以让我们的异步代码看起来和同步代码一样。让我们来看看await和async都能做什么吧。

await可以等待一个promise运行到完成状态并且获取结果,或者等待一个async修饰的函数运行完成并获取结果,但是使用await的时候,必须在async函数体内部。比如我有这样一个网络请求:

 function postRequest(posturl, postdata) {

     return new Promise((resolve, reject) => {

       wx.request({

         //省略其他属性

         success: function (res) {

           console.log("at post request: 请求成功")

           resolve(res.data)//设置promise成功标志

         },

         fail: function (res) {

           console.log("at post request: 请求失败")

           reject(res.data)//设置promise失败标志

         }

       })

     });

   }

那么如果不使用await,我就需要这样取得请求结果

function test(){
  postRequest(xxx,xxx).then(function(res){
      // 这里面可以读取请求结果res了
      console.log(res);
  });
}
test();

可以看到,这样的代码不太符合常规逻辑,我们希望函数作用是返回数据,这样更清晰明了,有了await,我们的愿望就可以实现了。

async function test(){
 let res = await  postRequest(xxx,xxx);
 // 下面就可以正常写对res的读取了
 console.log(res);
}
test();

注意我给函数加上了async,有了async和await,我们就可以像同步代码一样使用异步请求了~
那么上面那个通过复杂的构建链完成的需求,通过await实现将会变得非常简单易懂。

async function jabingp_upLoad(uploadurl, files) {
    let response = [];
  
    // 循环依次等待上传结果
    for (let i = 0; i < files.length; i++) {
        let  res = await jabingp_upLoadSingle(uploadurl, files[i]);
        // 结果放入数组
        response.push(res);
    }
    // 返回结果
    return  response ;
}

代码一下子变得简洁易懂了,注意调用的时候也同样需要在一个async函数内部执行await。

async function test(){
 let response = await  jabingp_upLoad(xxx,xxx);
 console.log(response );
}
test();

是不是非常简单呢,赶紧在你的异步请求中使用async和await吧~

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/104095.html

相关文章

  • 前端面试--浏览器

    摘要:打开一个网页,看到服务器返回给客户端浏览器的各种文件类型图片构建浏览器会遵守一套步骤将文件转换为树。因为浏览器有渲染线程与引擎线程,为了防止渲染出现不可预期的结果,这两个线程是互斥的关系。 1. 浏览器架构 用户界面 主进程 内核 渲染引擎 JS 引擎 执行栈 事件触发线程 消息队列 微任务 宏任务 网络异步线程 定时器线程 2. 从输入 url 到页面展示...

    bigdevil_s 评论0 收藏0
  • TiDB 源码阅读系列文章(十九)tikv-client(下)

    摘要:是什么在介绍的概念之前,我们需要简单回顾一下前面源码阅读系列文章六中讲过的和的概念以及它们和语句的关系。的任务就是实现请求,执行所有涉及到的请求,并依次返回结果。构造出了所有之后,下一步就是执行这些了。 上篇文章 中,我们介绍了数据读写过程中 tikv-client 需要解决的几个具体问题,本文将继续介绍 tikv-client 里的两个主要的模块——负责处理分布式计算的 copIte...

    whataa 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<