如果在你的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代码正确。

本篇博文全部内容已更新完毕。

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐