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


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 相关文章推荐
JS event使用方法详解
Apr 28 Javascript
Jquery遍历节点的方法小集
Jan 22 Javascript
js用typeof方法判断undefined类型
Jul 15 Javascript
laytpl 精致巧妙的JavaScript模板引擎
Aug 29 Javascript
JS+CSS实现美化的下拉列表框效果
Aug 11 Javascript
JavaScrip常见的一些算法总结
Dec 28 Javascript
理解javascript对象继承
Apr 17 Javascript
JSON与XML的区别对比及案例应用
Nov 11 Javascript
Vue渲染函数详解
Sep 15 Javascript
React学习之事件绑定的几种方法对比
Sep 24 Javascript
详解react-redux插件入门
Apr 19 Javascript
JS Array.from()将伪数组转换成数组的方法示例
Mar 23 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面向对象全攻略 (十七) 自动加载类
2009/09/30 PHP
phplock(php进程锁) v1.0 beta1
2009/11/24 PHP
Zend framework处理一个http请求的流程分析
2010/02/08 PHP
php 将字符串按大写字母分隔成字符串数组
2010/04/30 PHP
php的4种常用运行方式详解
2016/12/22 PHP
php简单随机字符串生成方法示例
2017/04/19 PHP
gearman中worker常驻后台,导致MySQL server has gone away的解决方法
2020/02/27 PHP
图片之间的切换
2006/06/26 Javascript
在jQuery1.5中使用deferred对象 着放大镜看Promise
2011/03/12 Javascript
基于jQuery实现的Ajax 验证用户名是否存在的实现代码
2011/04/06 Javascript
JqueryMobile动态生成listView并实现刷新的两种方法
2014/03/05 Javascript
javascript数字时钟示例分享
2014/04/23 Javascript
JS去除iframe滚动条的方法
2015/04/01 Javascript
jQuery validate插件submitHandler提交导致死循环解决方法
2016/01/21 Javascript
Vue键盘事件用法总结
2017/04/18 Javascript
vue加载完成后的回调函数方法
2018/09/07 Javascript
手把手教你写一个微信小程序(推荐)
2018/10/17 Javascript
[02:28]DOTA2 2017国际邀请赛小组赛回顾
2017/08/09 DOTA
zbar解码二维码和条形码示例
2014/02/07 Python
分享Pycharm中一些不为人知的技巧
2018/04/03 Python
Python开发虚拟环境使用virtualenvwrapper的搭建步骤教程图解
2018/09/19 Python
浅析PEP572: 海象运算符
2019/10/15 Python
Pytorch 中retain_graph的用法详解
2020/01/07 Python
通过python连接Linux命令行代码实例
2020/02/18 Python
python 爬虫爬取京东ps4售卖情况
2020/12/18 Python
全网最全python库selenium自动化使用详细教程
2021/01/12 Python
html5图片上传预览示例分享
2014/04/14 HTML / CSS
Perfume’s Club澳大利亚官网:西班牙领先的在线美容店
2021/02/01 全球购物
为什么在使用动态 SQL 语句时必须为低层数据库对象授予权限
2012/12/13 面试题
前台文员个人求职信范文
2014/01/05 职场文书
幼儿园小班评语
2014/04/18 职场文书
公司晚宴祝酒词
2015/08/11 职场文书
初中政治教学反思
2016/02/23 职场文书
如何写一份具有法律效力的借款协议书?
2019/07/02 职场文书
PHP中strval()函数实例用法
2021/06/07 PHP
Python爬虫 简单介绍一下Xpath及使用
2022/04/26 Python