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


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 要点归纳(三) jQuery中的事件和动画(下:动画篇)
Mar 24 Javascript
JavaScript中实现异步编程模式的4种方法
Sep 24 Javascript
jQuery中:disabled选择器用法实例
Jan 04 Javascript
JS实现两表格里数据来回转移的方法
May 28 Javascript
window.onload使用指南
Sep 13 Javascript
javascript中window.open在原来的窗口中打开新的窗口(不同名)
Nov 15 Javascript
利用JS轻松实现获取表单数据
Dec 06 Javascript
在js代码拼接dom对象到页面上去的模板总结(必看)
Feb 14 Javascript
Gulp实现静态网页模块化的方法详解
Jan 09 Javascript
利用Console来Debug的10个高级技巧汇总
Mar 26 Javascript
Vuejs中的watch实例详解(监听者)
Jan 05 Javascript
vue实现书本翻页动画效果实例详解
Apr 08 Vue.js
深入解析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
Classes and Objects in PHP5-面向对象编程 [1]
2006/10/09 PHP
php生成zip压缩文件的方法详解
2013/06/09 PHP
PHP实现四种基础排序算法的运行时间比较(推荐)
2016/08/11 PHP
php封装json通信接口详解及实例
2017/03/07 PHP
PHP格式化显示时间date()函数代码
2018/10/03 PHP
PHP中用Trait封装单例模式的实现
2019/12/18 PHP
js中字符替换函数String.replace()使用技巧
2011/08/14 Javascript
固定表格行列(expression)在IE下适用
2013/07/25 Javascript
javascript实现json页面分页实例代码
2014/02/20 Javascript
JS弹出对话框实现方法(三种方式)
2015/12/18 Javascript
JavaScript实现相册弹窗功能(zepto.js)
2016/06/21 Javascript
Bootstrap 模态框实例插件案例分析
2016/12/28 Javascript
过期软件破解办法实例详解
2017/01/04 Javascript
footer定位页面底部(代码分享)
2017/03/07 Javascript
React Native实现进度条弹框的示例代码
2017/07/17 Javascript
AngularJS 实现购物车全选反选功能
2017/10/24 Javascript
详解Next.js页面渲染的优化方案
2019/01/27 Javascript
jQuery操作cookie的示例代码
2019/06/05 jQuery
layui 富文本图片上传接口与普通按钮 文件上传接口的例子
2019/09/23 Javascript
js对象属性名驼峰式转下划线的实例代码
2020/09/17 Javascript
[54:08]LGD女子刀塔学院 DOTA2炼金术士教学
2014/01/09 DOTA
Python 字符串中的字符倒转
2008/09/06 Python
详解Python装饰器由浅入深
2016/12/09 Python
python中字符串类型json操作的注意事项
2017/05/02 Python
Python实用技巧之利用元组代替字典并为元组元素命名
2018/07/11 Python
django+xadmin+djcelery实现后台管理定时任务
2018/08/14 Python
设置python3为默认python的方法
2018/10/31 Python
Python datetime和unix时间戳之间相互转换的讲解
2019/04/01 Python
python NumPy ndarray二维数组 按照行列求平均实例
2019/11/26 Python
使用python动态生成波形曲线的实现
2019/12/04 Python
解决安装pyqt5之后无法打开spyder的问题
2019/12/13 Python
python学生信息管理系统实现代码
2019/12/17 Python
HTML5中div、article、section的区别及使用介绍
2013/08/14 HTML / CSS
MONNIER Frères英国官网:源自巴黎女士奢侈品配饰电商平台
2018/12/06 全球购物
大学生职业生涯规划书前言
2014/01/09 职场文书
个人租房协议书范本
2014/09/30 职场文书