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


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用unbind方法去掉hover事件及其他方法介绍
Mar 18 Javascript
js冒泡、捕获事件及阻止冒泡方法详细总结
May 08 Javascript
关于javaScript注册click事件传递参数的不成功问题
Jul 18 Javascript
JS实现的网页倒计时数字时钟效果
Mar 02 Javascript
chrome不支持form.submit的解决方案
Apr 28 Javascript
JavaScript encodeURI 和encodeURIComponent
Dec 04 Javascript
基于javascript实现样式清新图片轮播特效
Mar 30 Javascript
关于两个jQuery(js)特效冲突的bug的解决办法
Sep 04 Javascript
微信小程序中使用javascript 回调函数
May 11 Javascript
jQuery实现表格冻结顶栏效果
Aug 20 jQuery
vue实现在线翻译功能
Sep 27 Javascript
Vue中的this.$options.data()和this.$data用法说明
Jul 26 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后台程序与Javascript的两种交互方式
2009/10/25 PHP
php集成动态口令认证
2016/07/21 PHP
谈谈PHP连接Access数据库的注意事项
2016/08/12 PHP
PHP session会话操作技巧小结
2016/09/27 PHP
jquery中的查找parents与closest方法之间的区别
2013/12/02 Javascript
探讨JavaScript中声明全局变量三种方式的异同
2013/12/03 Javascript
离开当前页面前使用js判断条件提示是否要离开页面
2014/05/02 Javascript
浅析JavaScript声明变量
2015/12/21 Javascript
简单封装js的dom查询实例代码
2016/07/08 Javascript
AngularJS入门教程之MVC架构实例分析
2016/11/01 Javascript
JS实现div模块的截图并下载功能
2017/10/17 Javascript
JavaScrip关于创建常量的知识点
2017/12/07 Javascript
基于vue.js无缝滚动效果
2018/01/25 Javascript
JavaScript实现随机点名小程序
2020/10/29 Javascript
[02:08]我的刀塔不可能这么可爱 胡晓桃_1
2014/06/20 DOTA
[02:50]【扭转乾坤,只此一招】DOTA2永雾林渊版本开启新篇章
2020/12/22 DOTA
python调用windows api锁定计算机示例
2014/04/17 Python
利用信号如何监控Django模型对象字段值的变化详解
2017/11/27 Python
python+matplotlib实现鼠标移动三角形高亮及索引显示
2018/01/15 Python
python模拟事件触发机制详解
2018/01/19 Python
python代码 输入数字使其反向输出的方法
2018/12/22 Python
详解利用Python scipy.signal.filtfilt() 实现信号滤波
2019/06/05 Python
Django结合ajax进行页面实时更新的例子
2019/08/12 Python
python中xlrd模块的使用详解
2021/02/01 Python
html5摇一摇代码优化包括DeviceMotionEvent等等
2014/09/01 HTML / CSS
详解HTML5 data-* 自定义属性
2018/01/24 HTML / CSS
html5的input的required使用中遇到的问题及解决方法
2018/04/24 HTML / CSS
Java TransactionAPI (JTA) 主要包含几部分
2012/12/07 面试题
测绘工程本科生求职信
2013/10/10 职场文书
应届毕业生求职信
2013/11/30 职场文书
2014年两会学习心得体会
2014/03/10 职场文书
结婚当天新郎保证书
2015/05/08 职场文书
捐书仪式主持词
2015/07/04 职场文书
Python实现随机生成迷宫并自动寻路
2021/06/13 Python
django中websocket的具体使用
2022/01/22 Python
如何利用React实现图片识别App
2022/02/18 Javascript