如何在uni-app项目里让多个uni.request()异步请求按顺序执行——封装uni.request()异步请求按同步的方式执行
如果在你的uni-app项目里,出现以下情况:1) 你希望用第1个uni.request请求返回的结果作为第2个uni.request请求的参数;2)如果你发现Storage里存储的数据根本获取不到,或者即使获取到也是上一次的历史数据,Storage传参失败;3)你希望出现的代码执行顺序并没有按照你的预期顺序执行;那么很显然,你需要让uni.request()异步请求变成同步请求,因为uni-ap
如果在你的uni-app项目里,出现以下情况:
1) 你希望用第1个uni.request请求返回的结果作为第2个uni.request请求的参数;
2)如果你发现Storage里存储的数据根本获取不到,或者即使获取到也是上一次没清空的历史数据,Storage传参失败;
3)你希望出现的代码执行顺序并没有按照你的预期顺序执行;
那么很显然,你需要让uni.request()异步请求变成同步请求,因为uni-app的官网没有提供同步请求方法uni.requestSync();
所以我们需要使用Promise去封装uni.request(),或者使用 async 和 await去封装uni.request(),这样
uni.request()就会按照同步的方式执行,就不会出现uni.request()中参数获得参数值早于其它的uni.request()请求返回的结果
接下来,我会分多个版本的小项目去录屏调式演示出现这种状况的原因:
温馨提示:每个演示项目里都会用的md5.js文件,我上传的md5.js文件下载地址如下:
链接:https://pan.baidu.com/s/1uANsO5zfh7Q4Lw0jvXPZgg
提取码:3wuo
本篇博文很长,请耐心阅读,即使uni-app的官网上也没有这么高质量的文章。
范例1:
项目结构截图如下:
如何启动真机调试app程序,请按如下截图说明操作,后面几个项目
范例中不再啰嗦同样的话:
commons\sign-tool.js文件代码如下:
var md5 = require('./md5.js');
module.exports = {
sign : function(){
var greeting = "welcome to my world";
var greetingmd5 = md5.hex_md5(greeting)
uni.setStorageSync("greeting",greeting);
uni.setStorageSync("greetingmd5",greetingmd5);
console.log('sign-tool.js 开始打印输出以下内容:');
console.log("明文:"+greeting);
console.log("密文:"+greetingmd5);
console.log('sign-tool.js 结束打印输出');
}
}
index\index.vue文件代码如下:
<template>
<view>
<button type="primary" @click="clickMe">测试Storage传参</button>
</view>
</template>
<script>
var _self;
var tool = require('../../commons/sign-tool.js');
export default {
data() {
return {
}
},
onLoad: function() {
_self = this;
},
methods: {
clickMe: function() {
tool.sign();
var greeting = uni.getStorageSync('greeting');
var greetingmd5 = uni.getStorageSync('greetingmd5');
console.log('greeting: ' + greeting);
console.log('greetingmd5: ' + greetingmd5);
}
}
}
</script>
整个v1版本的项目代码已全部贴完,我将录屏演示调试代码的执行流程,即使你知道代码会怎么运行,
再看看我发的小视频又不碍啥事,因为后面的几个项目范例的调试视频你是一定会看的。
范例1演示调试代码的视频如下:
链接:https://pan.baidu.com/s/1PoathVeL6yY8m2vZkn5IzQ
提取码:4azu
范例1调试工具的控制台会输出如下截图内容:
根据调试代码的视频和输出结果,我们可以得出如下结论:
本范例流程执行正常 能按照我们预期的代码执行顺序运行。
范例2:
接下来演示的几个项目范例,我们都会用到PHP实现的Restful api 接口,用Redis作为数据存储服务器,
我将使用ThinkPHP 6.0.2这个最新版本框架来写后台数据接口。
在演示范例2之前,我们需要先把后台数据接口写好,接下来我们先来写PHP实现的后台代码吧。
phpStudy v8.1版 下载地址:https://www.xp.cn/download.html 安装到D:\phpstudy
Redis for Windows 64位版 下载地址:https://github.com/microsoftarchive/redis/releases/download/win-3.0.504/Redis-x64-3.0.504.msi
ThinkPHP6.0.2 下载地址:https://github.com/top-think/think/archive/v6.0.2.zip 解压到(WWW文件夹不包括v6.0.2文件夹) D:\phpstudy\WWW
composer下载地址:https://getcomposer.org/Composer-Setup.exe
安装composer.exe程序,一路Next就好,
当安装出现Choose the command-line PHP you want to use:请浏览选择php.exe文件的完整路径
D:\phpstudy\Extensions\php\php7.3.4nts\php.exe
接下来以更新安装的方式安装ThinkPHP项目需要的一切依赖文件
C:\Users\ZiGoo>D:
D:\>cd D:\phpstudy\WWW
D:\phpstudy\WWW>composer update topthink/framework
WWW\config\cache.php文件代码如下:
<?php
// +----------------------------------------------------------------------
// | 缓存设置
// +----------------------------------------------------------------------
return [
// 默认缓存驱动
'default' => env('cache.driver', 'file'),
// 缓存连接方式配置
'stores' => [
'file' => [
// 驱动方式
'type' => 'File',
// 缓存保存目录
'path' => '',
// 缓存前缀
'prefix' => '',
// 缓存有效期 0表示永久缓存
'expire' => 0,
// 缓存标签前缀
'tag_prefix' => 'tag:',
// 序列化机制 例如 ['serialize', 'unserialize']
'serialize' => [],
],
// redis缓存
'redis' => [
// 驱动方式
'type' => 'redis',
// 服务器地址
'host' => '127.0.0.1',
// 缓存前缀
'prefix' => '',
// 缓存有效期 0表示永久缓存
'expire' => 0,
// 序列化机制 例如 ['serialize', 'unserialize']
'serialize' => [],
],
// 更多的缓存连接
],
];
WWW\route\app.php文件代码如下:
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
use think\facade\Route;
// GET 'http://2977a2v278.zicp.vip/public/index.php/zigoo.token.generate'
Route::get('zigoo.token.generate','Token/generateToken');
// GET http://2977a2v278.zicp.vip/public/index.php/zigoo.tutorial.sign.call
Route::get('zigoo.tutorial.sign.call','Tutorial/callSign');
WWW\app\controller\Token.php文件代码如下:
<?php
namespace app\controller;
use app\BaseController;
use think\facade\Log;
use think\facade\Cache;
class Token extends BaseController {
/*
[myth@contoso ~]$ curl --location --request GET 'http://2977a2v278.zicp.vip/public/index.php/zigoo.token.generate'
{"status":"ok","data":{"token":"qn0aulb59sj293fh9qiijoecn8","time":1589476476}}
[myth@contoso ~]$
*/
public function generateToken(){
$session_create_id = session_create_id();
$time = time();
$token = array(
'token' => $session_create_id,
'time' => $time
);
Cache::store('redis')->set($token['token'],$token['time'],3600);
exit(json_encode(['status' => 'ok','data' => $token]));
}
}
WWW\app\controller\Tutorial.php文件代码如下:
<?php
namespace app\controller;
use app\BaseController;
use think\facade\Log;
use think\facade\Cache;
class Tutorial extends BaseController {
//调用签名 api 接口
public function callSign(){
$version = $this->request->param('version');
Cache::store('redis')->incr('counter'); //记录接口被访问的次数
checkSign();//如果用户想获得本接口返回的数据,必须通过签名验证
return json_encode(['status' => 'ok', 'data' => '你访问的是被签名保护的Restful api 接口','version' => $version]);
}
}
WWW\app\common.php文件代码如下:
<?php
// 应用公共文件
use think\facade\Request;
use think\facade\Cache;
use think\facade\Log;
// 签名验证
function checkSign(){
$params = Request::param();
exit(json_encode($params));
//具体验证代码省略 ... ...
}
PHP后台代码已全部贴完。
uni-app项目结构截图如下:
commons\sign-tool.js文件代码如下:
var md5 = require('./md5.js');
module.exports = {
sign : function(apiServer,params = {}){
uni.request({
url: apiServer+'zigoo.token.generate',
method: 'GET',
success: res => {
console.log(res);
if(res.data.status != 'ok'){return ;}
var data = res.data.data;
console.log('明文:token'+data.token +' time:' + data.time);
var datamd5 = md5.hex_md5(data.token + data.time) + '-' + data.token;
console.log('密文:'+ datamd5);
// 记录在本地
uni.setStorageSync("token",datamd5);
uni.setStorageSync("time",data.time);
},
fail:function(e){
console.log(JSON.stringify(e));
}
});
}
}
index\index.vue文件代码如下:
<template>
<view>
<button type="primary" @click="clickMe">测试Storage传参</button>
</view>
</template>
<script>
var _self;
var tool = require('../../commons/sign-tool.js');
export default {
data() {
return {
}
},
onLoad: function() {
_self = this;
},
methods: {
clickMe: function() {
var version = 'api v1.0.1';
//请注意,我们预期是想首先调用tool.sign()函数,获取PHP服务器上的返回的数据作为下一个GET请求的参数值
tool.sign(_self.apiServer, {
version: version
});
//请注意调试视频里uni.getStorageSync()函数被提前执行啦,这是不对的
var token = uni.getStorageSync('token');
var time = uni.getStorageSync('time');
uni.request({ //uni.request()函数是异步请求函数,官网没有提供同步uni.requestSync()函数
url: _self.apiServer + 'zigoo.tutorial.sign.call',
method: 'GET',
data: {
version: version,
sign: token, // 因为token提前获得的值是个空值,显然代码逻辑上是不对的
time: time // 因为time提前获得的值是个空值,显然代码逻辑上是不对的
},
success(res) {
//打印GET请求发送出去的参数内容,res的内容是由PHP服务器返回的
//请注意,PHP服务器收到的sign参数值和time参数值均为空值
console.log(res);
uni.setStorageSync("token",'');
uni.setStorageSync("time",'');
},
fail(e) {
console.log(e);
}
});
}
}
}
</script>
main.js文件代码如下:
import Vue from 'vue'
import App from './App'
Vue.config.productionTip = false
Vue.prototype.apiServer = 'http://2977a2v278.zicp.vip/public/index.php/';
App.mpType = 'app'
const app = new Vue({
...App
})
app.$mount()
范例2演示调试代码的视频如下:
链接:https://pan.baidu.com/s/1e-GK0lYFDkE1drp89LNQ1w
提取码:d9sh
从调试代码的执行流程视频中,我们可以得出这样的结论:
多个异步请求uni.request()函数,全部异步的请求函数,每个请求输入参数的初始化都赋值完毕后,
才执行第1个异步请求,这样代码的执行逻辑就不对啦,我们需要的是第1个异步请求返回值作为第2
个异步请求的输入参数,因为异步请求输入参数获得参数值的执行时间早于所有异步请求GET和POST请求发出。
所以Storage传参就自然失败啦,这是个典型的错误范例,在范例3中我将封装异步请求按照同步的方式执行来更正这个错误的范例。
范例3:
项目结构截图如下,这是一个正确的范例:
commons\sign-tool.js文件代码如下:
var md5 = require('./md5.js');
module.exports = {
// 注意async 和 await 的修饰写法,意思是让异步请求uni.request()封装到同步函数sign里执行
async sign(apiServer,params = {}){
// GET请求返回数据:{"data":{"status":"ok","data":{"token":"5qraetfdq8kqi339u4m2if6k0e","time":1589808256}}
var [e,res] = await uni.request({
url: apiServer+'zigoo.token.generate',
method: 'GET'
});
console.log(res);
if(res.data.status != 'ok'){return ;}
var data = res.data.data;
console.log('明文:token'+data.token +' time:' + data.time);
var datamd5 = md5.hex_md5(data.token + data.time) + '-'+ data.token;
console.log('密文:'+ datamd5);
// 记录在本地
uni.setStorageSync("token", datamd5); // 1f3907e22469764866dce02aba5e26a2-5qraetfdq8kqi339u4m2if6k0e
uni.setStorageSync("time", data.time); // 1589808256
/*uni.setStorage({
key: 'token',
data: datamd5,
success: function () {
console.log('success1');
}
});
uni.setStorage({
key: 'time',
data: data.time,
success: function () {
console.log('success2');
}
});*/
return;
}
}
index\index.vue文件代码如下:
<template>
<view>
<button type="primary" @click="clickMe">测试Storage传参</button>
</view>
</template>
<script>
var _self;
var tool = require('../../commons/sign-tool.js');
export default {
data() {
return {
}
},
onLoad: function() {
_self = this;
},
methods: {
//注意 async 和 await 的修饰写法,封装后的uni.request异步请求会按照requestSync函数同步的方式执行
requestWithSign : async function(apiServer,params = {}){
var token = uni.getStorageSync('token');
var time = uni.getStorageSync('time');
var [e,res] = await uni.request({
url: apiServer + 'zigoo.tutorial.sign.call',
method: 'GET',
data: {
version: params.version,
sign: token,
time: time
}
});
// 注意本范例的特点:前一个异步uni.request请求的返回值作为后一个异步uni.request请求参数
// 本范例实际上演示的是将传输给服务器的参数,让服务器给直接返回
// GET 请求返回:{"data":{"version":"api v1.0.1","sign":"1f3907e22469764866dce02aba5e26a2-5qraetfdq8kqi339u4m2if6k0e","time":1589808256}}
console.log(res);
uni.setStorageSync("token",'');
uni.setStorageSync("time",'');
return;
},
//注意 async 和 await 的修饰写法,clickMe同步函数内部一直要等到同步函数tool.sign()执行完毕,再执行同步函数_self.requestSync()
async clickMe() {
var version = 'api v1.0.1';
await tool.sign(_self.apiServer, {
version: version
});
await _self.requestWithSign(_self.apiServer,{
version: version
});
/*
var token = await uni.getStorage({
key: 'token',
success: function (res) {
return res.data.token;
}
});
var time = await uni.getStorage({
key: 'time',
success: function (res) {
return res.data.time;
}
});*/
}
}
}
</script>
main.js文件代码如下:
import Vue from 'vue'
import App from './App'
Vue.config.productionTip = false
Vue.prototype.apiServer = 'http://2977a2v278.zicp.vip/public/index.php/';
App.mpType = 'app'
const app = new Vue({
...App
})
app.$mount()
范例3演示调试代码的视频如下:
链接:https://pan.baidu.com/s/1FThCIzp6J4j_wX9aP-MFOw
提取码:p8mq
从调试代码的视频中,我们很直观看到代码能够按照我们期望的自然顺序来执行代码啦,结论:范例3代码正确。
接下来在范例4中我将使用Promise来替代async和await来实现范例3同样的同步执行效果。
范例4:
项目结构截图如下,这也是一个正确的范例:
commons\sign-tool.js文件代码如下:
var md5 = require('./md5.js');
module.exports = {
sign(apiServer, params = {}) {
//注意 Promise取代 async 和 await的写法,同步函数sign把异步请求uni.request封装成同步的方式执行
return new Promise((resolve, reject) => {
// GET请求返回数据:{"data":{"status":"ok","data":{"token":"5qraetfdq8kqi339u4m2if6k0e","time":1589808256}}
uni.request({
url: apiServer + 'zigoo.token.generate',
method: 'GET',
success: (res) => {
console.log(res);
if (res.data.status != 'ok') {
return;
}
var data = res.data.data;
console.log('加密之前:token' + data.token + ' time:' + data.time);
var datamd5 = md5.hex_md5(data.token + data.time) + '-'+ data.token;
console.log('加密输出结果:' + datamd5);
// 记录在本地
uni.setStorageSync("token", datamd5); // 1f3907e22469764866dce02aba5e26a2-5qraetfdq8kqi339u4m2if6k0e
uni.setStorageSync("time", data.time); // 1589808256
resolve(res);
},
fail: (err) => {
console.log('request fail', err)
reject(err)
}
})
})
}
}
index\index.vue文件代码如下:
<template>
<view>
<button type="primary" @click="clickMe">测试Storage传参</button>
</view>
</template>
<script>
var _self;
var tool = require('../../commons/sign-tool.js');
export default {
data() {
return {
}
},
onLoad: function() {
_self = this;
},
methods: {
requestWithSign(apiServer,params = {}) {
// Promise 取代 async 和 await写法
return new Promise((resolve, reject) => {
var token = uni.getStorageSync('token');
var time = uni.getStorageSync('time');
uni.request({
url: apiServer + 'zigoo.tutorial.sign.call',
method: 'GET',
data: {
version: params.version,
sign: token,
time: time
},
success: (res) => {
// 注意本范例的特点:前一个异步uni.request请求的返回值作为后一个异步uni.request请求参数
// 本范例实际上演示的是将传输给服务器的参数,让服务器给直接返回
// GET 请求返回:{"data":{"version":"api v1.0.1","sign":"1f3907e22469764866dce02aba5e26a2-5qraetfdq8kqi339u4m2if6k0e","time":1589808256}}
console.log(res);
uni.setStorageSync("token", '');
uni.setStorageSync("time", '');
resolve(res);
},
fail: (err) => {
console.log('request fail', err)
reject(err)
}
})
})
},
// 注意async 和 await的修饰写法,这样异步请求就按照同步的方式执行,不然程序就不会按照我们希望的代码执行顺序执行啦
async clickMe() {
var version = 'api v1.0.1';
await tool.sign(_self.apiServer, {
version: version
});
await _self.requestWithSign(_self.apiServer, {
version: version
});
/*
var token = await uni.getStorage({
key: 'token',
success: function (res) {
return res.data.token;
}
});
var time = await uni.getStorage({
key: 'time',
success: function (res) {
return res.data.time;
}
});*/
}
}
}
</script>
main.js文件代码如下:
import Vue from 'vue'
import App from './App'
Vue.config.productionTip = false
Vue.prototype.apiServer = 'http://2977a2v278.zicp.vip/public/index.php/';
App.mpType = 'app'
const app = new Vue({
...App
})
app.$mount()
范例4演示调试代码的视频如下,个人感觉范例4的代码写法没有范例3运行得流畅:
链接:https://pan.baidu.com/s/1vLDbfB25At8oAWI_ujCl3w
提取码:t74g
从调试代码的视频中,我们很直观看到代码能够按照我们期望的自然顺序来执行代码啦,结论:范例4代码正确。
本篇博文全部内容已更新完毕。
更多推荐
所有评论(0)