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


Posted in Javascript onMay 05, 2019

业务逻辑

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

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

  1. 首先检测用户是否传入了图片,如果存储本地图片地址的数组长度>=1,则将图片数组放入上传图片的函数。
  2. 由于小程序网络请求大小限制,我们只能采取循环上传单文件,然后收集每次请求的结果--图片在服务器的地址,最后将结果放在一个数组中供后续的操作使用。
  3. 当图片上传函数全部执行完毕后,将数组中的图片数组取出来,赋值到日记对象中,再将整个日记对象提交到服务器。
  4. 服务器返回保存成功或失败。

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

异步带来的问题

小程序的网络请求是异步的:我们无法通过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吧~

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
jQuery库与其他JS库冲突的解决办法
Feb 07 Javascript
修复bash漏洞的shell脚本分享
Dec 31 Javascript
jQuery实现TAB风格的全国省份城市滑动切换效果代码
Aug 24 Javascript
JavaScript获取当前cpu使用率的方法
Dec 15 Javascript
基于jquery实现简单的分页控件
Mar 17 Javascript
在html中引入外部js文件,并调用带参函数的方法
Oct 31 Javascript
jQuery实现的回车触发按钮事件功能示例
Mar 25 jQuery
用图片替换checkbox原始样式并实现同样的功能
Nov 15 Javascript
javascript for循环性能测试示例
Aug 07 Javascript
快速对接payjq的个人微信支付接口过程解析
Aug 15 Javascript
详解如何修改 node_modules 里的文件
May 22 Javascript
JavaScript常用工具函数汇总(浏览器环境)
Sep 17 Javascript
深入解析Vue源码实例挂载与编译流程实现思路详解
May 05 #Javascript
Vue 中如何正确引入第三方模块的方法步骤
May 05 #Javascript
详解vue的双向绑定原理及实现
May 05 #Javascript
Vue+Express实现登录注销功能的实例代码
May 05 #Javascript
Vue 递归多级菜单的实例代码
May 05 #Javascript
微信小程序自定义组件传值 页面和组件相互传数据操作示例
May 05 #Javascript
详解Vue调用手机相机和相册以及上传
May 05 #Javascript
You might like
PHP设计模式之解释器模式的深入解析
2013/06/13 PHP
php文件压缩之PHPZip类用法实例
2015/06/18 PHP
Laravel框架Eloquent ORM修改数据操作示例
2019/12/03 PHP
Javascript实现的分页函数
2006/12/22 Javascript
JavaScript的parseInt 取整使用
2011/05/09 Javascript
40个新鲜出炉的jQuery 插件和免费教程[上]
2012/07/24 Javascript
JS实现动态增加和删除li标签行的实例代码
2016/10/16 Javascript
微信小程序 定义全局数据、函数复用、模版等详细介绍
2016/10/27 Javascript
jquery滚动条插件(可以自定义)
2016/12/11 Javascript
使用nodejs下载风景壁纸
2017/02/05 NodeJs
html5+canvas实现支持触屏的签名插件教程
2017/05/08 Javascript
React + webpack 环境配置的方法步骤
2017/09/07 Javascript
Postman环境变量全局变量使用方法详解
2020/08/13 Javascript
Javascript实现打鼓效果
2021/01/29 Javascript
JavaScript Dom实现轮播图原理和实例
2021/02/19 Javascript
python制作花瓣网美女图片爬虫
2015/10/28 Python
图文讲解选择排序算法的原理及在Python中的实现
2016/05/04 Python
举例讲解Python编程中对线程锁的使用
2016/07/12 Python
python+opencv实现的简单人脸识别代码示例
2017/11/14 Python
python实现基于信息增益的决策树归纳
2018/12/18 Python
Python发展简史 Python来历
2019/05/14 Python
基于python进行抽样分布描述及实践详解
2019/09/02 Python
python 实现学生信息管理系统的示例
2020/11/28 Python
请问软件开发中的设计模式你会使用哪些
2015/05/13 面试题
儿科护士自我鉴定
2013/10/14 职场文书
客服主管岗位职责
2013/12/13 职场文书
创先争优活动承诺书
2014/08/30 职场文书
国际贸易本科毕业生求职信
2014/09/26 职场文书
2014年应急管理工作总结
2014/11/26 职场文书
医务人员医德考评自我评价
2015/03/03 职场文书
员工升职自荐信
2015/03/27 职场文书
淘宝客服专员岗位职责
2015/04/07 职场文书
入党介绍人意见2015
2015/06/01 职场文书
海洋天堂观后感
2015/06/05 职场文书
宣传部部长竞选稿
2015/11/21 职场文书
mysql 直接拷贝data 目录下文件还原数据的实现
2021/07/25 MySQL