最近做了一个项目, 是国内做前端开发, 绕不开的一个平台: 微信生态! 之前做过微信小程序, 这回这个则是微信公众号页面, 个人觉得微信小程序的开发体验比微信公众号页面的开发体验更好, 私以为是因为小程序的授权以及各种
API
更完善, 当然了, 也可能是因为我做的这个小程序比较简单吧, 关于微信公众号页面的具体内容可查看这篇文章:
vue3+vant开发微信公众号网页爬坑不完全指北
这次的微信公众号开发前前后后有一个月的时间, 业务逻辑不算复杂, 但也不少, 主要是和微信进行各种交互, 各种流程, 这就少不了
异步逻辑
的处理, 对于前端开发来说, 异步逻辑的解决方案, 最著名和好用的莫过于
async/await+promise
了, 关于这个异步解决方案, 我之前也发过一篇相关的文章, 感兴趣的小伙伴可以看看:
ES8 async/await: 优雅的异步编程解决方案
, 而我个人还看过一篇非常著名的阮一峰老师的文章, 也就是大名鼎鼎的
ECMAScript 6入门
里的这篇文章:
async 函数_ECMAScript 6 入门
本篇文章主要是从一些概念原理, 尤其是实际编写之后才发现的出发, 聊一聊我遇到的
async/await+promise
的实际使用场景, 那么话不多说, 我们正式开始
Promise
Promise.all等静态方法本文就不讨论了, 有需要的小伙伴可查阅阮一峰老师这本书的Promise相关章节:
Promise 对象
首先还是我们所熟悉的
Promise
对象, 由
Promise
构造函数
生成:
const promise = new Promise((resolve, reject) => {
if() {
resolve(value);
}else{
reject(error);
Promise 构造函数接收一个函数作为参数, 这个函数还有两个参数, 第一个参数我们叫resolve, 第二个我们叫reject, 分别是解决和拒绝的意思
返回的Promise对象一共有3种状态: pending, fulfilled以及rejected
我们通过new操作符调用Promise的时候就是执行它的构造函数, 此时也会执行构造函数的参数, 以上面的代码为例就是会执行传递到Promise中的箭头函数, 此时返回的Promise对象的状态为pending, 当我们调用resolve或者reject的时候, Promise对象的状态将会改变: 调用resolve会使Promise对象的状态由pending变为fulfilled, 调用reject则会使其状态从pending变为rejected
同时需要注意的是, Promise的状态只能被改变一次, 要么从pengding变为fulfilled, 要么从pending变为rejected, fulfilled和rejected之间不能互相变换更不能变回pending, 以及状态从pending变为rejected的时候将会抛出一个错误
resolve被调用时传递的参数将会变成Promise对象的结果, 该结果可以通过Promise对象的then方法获取到, 而reject被调用时传递的参数则会被Promise对象的catch方法获取到:
promise
.then((res) => {
console.log(res);
.catch((error) => {
console.log(error);
也就是说Promise对象的状态为fulfilled(调用了resolve方法之后)时, 我们能调用then方法获取resolve传递出来的参数, 而当它的状态是rejected(调用了reject方法之后)时, 我们则能调用catch方法获取reject传递出来的参数
这里能使用链式调用是因为Promise的then方法和catch方法都会返回一个Promise对象
async/await
await
await关键字必须要在async函数中使用, await的功能就像它的意思一样, 等待, 表示后面的表达式要等待一个结果, 而这个表达式一般是返回一个Promise对象, 也就是说await后面一般跟能返回Promise对象的表达式, 同时只有await等待的结果为fulfilled的时候才会执行它后面的语句, pending和rejected的时候都不会执行
简单来说就是await关键字会等待它后面的Promise fulfilled之后将Promise的结果做一个返回, 然后再接着执行后续的语句
由于await后面需要跟一个能返回Promise的表达式, 因此我们这里写一个函数, 它的返回值是一个Promise, 我这里用的是箭头函数, 它本质上是一个函数表达式, 写成函数声明的形式也是可以的, 但个人习惯, 因此还是写成箭头函数的形式, 这里也附上函数声明的写法:
async function foo() {
后续的写法均会使用箭头函数的形式:
const p = () => {
return new Promise((resolve, reject) => {
setTimeout(
() => {
resolve(123);
const foo = async () => {
const res = await p();
console.log(res);
console.log('后续代码');
foo();
此时2秒之后才会打印res和后续代码, 因为new的时候Promise的状态为pending, 2秒之后变为了fulfilled, 而如果是这样的情况:
const p = () => {
return new Promise((resolve, reject) => {
const foo = async () => {
const res = await p();
console.log(res);
console.log('后续代码');
foo();
await后面的两个输出语句永远不会执行, 因为此时await后面的表达式返回的Promise对象的状态是pending的, 接下来再看看这段代码:
const p = () => {
return new Promise((resolve, reject) => {
reject('一个错误');
const foo = async () => {
const res = await p();
console.log(res);
console.log('后续代码');
foo();
此时await后面的两个输出语句也是永远不会执行, 因为Promise对象的状态是rejected的, 也就是说: async函数中, await语句后面的语句要执行, 当且仅当这个await后面的Promise的状态是fulfilled的时候才行
而await最典型的用法就是继发的操作, 一个执行完成再执行下一个:
const p1 = () => {
return new Promise((resolve, reject) => {
setTimeout(
() => {
resolve('p1');
const p2 = () => {
return new Promise((resolve, reject) => {
resolve('p2');
const foo = async () => {
const res1 = await p1();
const res2 = await p2();
console.log(res1);
console.log(res2);
foo();
这里2秒之后p1的状态变为fulfilled, 然后p2才会执行
这里可能有小伙伴要问了: rejected了怎么办? 这个问题我会放到接下来的内容中和大家探讨
async
现在我们知道await关键字必须要在async函数中才能使用, 相当于async给await提供了一个作用域, 但除此之外async还有什么别的作用吗? 私以为是有的, 因为我们的async函数只要执行了, 就会返回一个Promise对象, 而且这个Promise对象的状态默认是fulfilled的, 但如果async函数内显式返回了一个Promise对象, 那么最终async函数返回的Promise就是里面显式返回的Promise对象, 最终状态由内部显式返回的Promise对象决定, 简单来说就是:
async内返回值不是Promise: 那个返回值会被Promise.resolve()处理之后返回, 此时async返回的Promise对象的状态为fulfilled
async内返回了一个Promise: 直接返回这个Promise, 此时async返回的Promise对象的状态为内部Promise的状态
返回值不是Promise对象
const foo = async () => {
const res = foo();
console.log(res);
此时我们可以看到打印的res是个Promise对象, 同时它的状态为fulfilled, 只是结果是undefined, 因为async函数内没有任何的return语句来返回一个值:
const foo = async () => {
foo().then((res) => {
console.log('res:', res);
这里可以看到打印的res是undefined, 此时我们尝试另一种写法:
const foo = async () => {
return 123;
foo().then((res) => {
console.log('res:', res);
此时打印的res就有值了, 是123
返回值是Promise对象
我们直接来看代码:
const p = () => {
return new Promise((resolve, reject) => {
reject('p rejected');
const foo = async () => {
return p();
foo()
.then((res) => {
console.log('res:', res);
.catch((error) => {
console.log('error:', error);
上面这段代码中, p函数返回了一个rejected的Promise, 然后async函数foo中直接返回这个Promise, 也就是说async的返回值已经是一个Promise对象了, 所以最终async返回的Promise就是p函数返回的Promise, 此时会进到catch回调, 因为p函数返回的是一个rejected的Promise
返回值前加不加await
先说结论哈: 个人认为是没必要加, 因为加不加结果都一样
async函数中返回Promise还有一种写法是在Promise前面加await关键字, 像这样:
const p = () => {
return new Promise((resolve, reject) => {
resolve('p resolved');
const foo = async () => {
return await p();
foo()
.then((res) => {
console.log('res:', res);
.catch((error) => {
console.log('error:', error);
再对比看一下这段代码:
const p = () => {
return new Promise((resolve, reject) => {
resolve('p resolved');
const foo = async () => {
return p();
foo()
.then((res) => {
console.log('res:', res);
.catch((error) => {
console.log('error:'
, error);
两段代码唯一的区别在于foo函数内的return语句, 前者有await关键字, 后者没有, 但最终结果是一样的, 都走到了foo的then回调中, 为何会是一样的呢?
在讨论这个问题之前, 我们把这两段代码修改一下:
const p = () => {
return new Promise((resolve, reject) => {
resolve('p resolved');
const foo = async () => {
const res = await p();
console.log('res:', res);
foo();
const p = () => {
return new Promise((resolve, reject) => {
resolve('p resolved');
const foo = async () => {
const res = p();
console.log('res:', res);
foo();
关键代码在foo函数内, 一个是打印await p(), 一个是直接打印p(), 根据上面Promise和await的知识能得出: await p()的返回值是字符串 p resolved, 而p()的返回值则是一个Promise, 再根据async的知识: async返回的结果是Promise则直接返回, 不是则会通过Promise.resolve处理之后返回可得出: 字符串 p resolved不是Promise对象, 它会被Promise.resolve处理成Promise, 处理成和p函数的返回值一样, 也就是说最终的结果都是p函数的返回值, 所以加不加await关键字, 结果都是一样的
处理多个异步操作
async函数有一个常用的做法是将多个await收敛起来做统一的处理, 也就是处理多个异步操作:
const p1 = () => {
return new Promise((resolve, reject) => {
resolve('p1');
const p2 = (params) => {
return new Promise((resolve, reject) => {
resolve(`${params}_p2`);
const foo = async () => {
const res1 = await p1();
const res2 = await p2(res1);
return res2;
foo().then((res) => {
console.log('res:', res);
这个使用方式也是async/await最常用的一个方式, 就是继发逻辑的处理, 而根据上面的知识, foo函数内也可以这么写:
const foo = async () => {
const res1 = await p1();
return p2(res1);
async函数一经调用就会返回一个fulfilled的Promise对象, 我们无法在其中根据条件来修改这个Promise对象的状态, 比如:
const foo = async () => {
let res = 1;
setTimeout(
() => {
res = 2;
return res;
foo()
.then((res) => {
console.log('res:', res);
.catch((error) => {
console.log('error:', error);
这段代码执行之后会走到foo().then方法中, 打印1, 哪怕使用了await关键字也不行:
const foo = async () => {
let res = 1;
await setTimeout(
() => {
res = 2;
return res;
foo()
.then((res) => {
console.log('res:', res);
.catch((error) => {
console.log('error:', error);
这段代码的运行结果和上面那段代码是一样的, 因此async函数的主要作用就是用来给await提供一个执行的作用域, 让我们能以同步的方式处理异步的逻辑
也就是说, 当我们需要处理一些逻辑, 在这些逻辑没有一个结果的时候Promise需要pending等待我们处理, 然后我们再根据处理的结果来决定这个Promise的状态究竟是fulfilled还是rejected, 此时只能使用一开始提到的Promise 构造函数来实现
错误情况的处理是对await后面的Promise对象而言的, 但await又必须要在async方法中使用, 因此这里结合async来探讨, 以及这里主要聊一聊常用的几个情形
报错之后不中断后续代码执行
const p = () => {
return new Promise((resolve, reject) => {
reject('p rejected');
const foo = async () => {
const res = await p().catch((error) => {
console.log('error:', error);
console.log('res:', res);
console.log('后续代码');
foo();
这样的写法能捕获到报错, 同时还不会中断后续代码的执行
还有一个种写法是使用try...catch...语句:
const p = () => {
return new Promise((resolve, reject) => {
reject('p rejected');
const foo = async () => {
try {
const res = await p();
console.log('res:', res);
} catch (error) {
console.log('error:', error);
console.log('后续代码');
foo();
但一般try...catch...语句主要用于有多个await的情况
报错之后中断后续代码执行
这个是比较常见的一个情形, 比如这样的一段代码:
const p1 = () => {
return new Promise((resolve, reject) => {
reject('p1 rejected');
const p2 = () => {
return new Promise((resolve, reject) => {
resolve('p2 resolved');
const foo = async () => {
try {
const res1 = await p1();
console.log('res1:', res1);
const res2 = await p2();
console.log('res2:', res2);
} catch (error) {
console.log('error:', error);
foo();
由于await的特性, 当p1返回的Promise rejected之后, 后续的代码就不会执行了, 而对于多个await且上一个报错要中断下一个的执行, 那么用try...catch...语句来处理再好不过了
但上述的错误处理都是在async函数内进行的, 还有一种是需要将报错返回出去的情况, 当然了, 实际情况是需要将async内得到的结果返回出去, 无论是否报错, 未报错的情形上面已经探讨过了, 这里主要来看看报错的情形
将async中的成功/错误结果返回
先说返回错误的情形:
const p1 = () => {
return new Promise((resolve, reject) => {
reject('p1 rejected');
const p2 = () => {
return new Promise((resolve, reject) => {
resolve('p2 resolved');
const foo = async () => {
const res1 = await p1();
console.log('res1:', res1);
const res2 = await p2();
console.log('res2:', res2);
console.log('error:', error);
foo()
.then((res) => {
console.log('res:', res);
.catch((error) => {
console.log('error:', error);
这样就可以了, 就能将错误返回出去了, 任意一个await后面的Promise rejected了, 后续代码就会终止执行, 同时这种情况还等同于async返回的Promise对象被reject了, 可以理解为async会捕获它里面的错误, 确切的说是异常, 当然错误Error也算一种异常, 比如:
const foo = async () => {
throw '出错了';
foo()
.then((res) => {
console.log('res:', res);
.catch((error) => {
console.log('error:', error);
const foo = async () => {
throw new Error('出错了');
foo()
.then((res) => {
console.log('res:', res);
.catch((error) => {
console.log('error:', error);
再来就是更常见的情况: 将最终结果返回, 无论这个结果是否报错, 此时我们可以这么做:
const p1 = () => {
return new Promise((resolve, reject) => {
resolve('p1');
const
p2 = (params) => {
return new Promise((resolve, reject) => {
resolve(`${params}_p2 resolved`);
const foo = async () => {
const res1 = await p1();
return p2(res1);
foo()
.then((res) => {
console.log('res:', res);
.catch((error) => {
console.log('error:', error);
这个情形中p1 p2都fulfilled了, foo中的代码依次执行, 最终返回p2 fulfilled的Promise, 再来就是报错:
const p1 = () => {
return new Promise((resolve, reject) => {
reject('p1 rejected');
const p2 = (params) => {
return new Promise((resolve, reject) => {
resolve(`${params}_p2 resolved`);
const foo = async () => {
const res1 = await p1();
return p2(res1);
foo()
.then((res) => {
console.log('res:', res);
.catch((error) => {
console.log('error:', error);
此时由于p1报错, 则后续p2不会执行, 会直接报错, 同时被async捕获, 所以此时async函数返回的Promise是p1 rejected的Promise
async中使用try...catch...并需要返回错误结果
这是我个人在实际开发工作中遇到的一个情况, 大致代码如下:
const p1 = () => {
return new Promise((resolve, reject) => {
reject('p1 rejected');
const p2 = (params) => {
return new Promise((resolve, reject) => {
resolve(`${params}_p2 resolved`);
const foo = async () => {
try {
const res1 = await p1();
return p2(res1);
} catch (error) {
return error;
foo()
.then((res) => {
console.log('res:', res);
.catch((error) => {
console.log('error:', error);
foo中执行了一个继发的异步操作, 成功则返回最终的结果, 也就是p2执行之后的结果, 任意一个报错了则返回这个错误, 但这个代码最终的执行结果却是进到了foo().then方法中, 而不是预期的foo().catch中, 这是为什么呢?
这是因为try...catch...中的catch捕获到的那个error已经不是一个Promise了, 而是一个异常, 在上面这个代码中, 这个异常
的类型是字符串, 而从上面async的知识中我们知道: 返回的结果如果不是Promise那么它会使用Promise.resolve处理一下然后再返回, 也就是说, 上面的foo函数中的代码等价于:
const foo = async () => {
try {
const res1 = await p1();
return p2(res1);
} catch (error) {
return Promise.resolve(error);
这么写也会进到foo().then方法中
项目中我在其他地方调用foo函数的时候遇到里面报错, 但最终foo函数返回的Promise居然是fulfilled的情况, 多方排查之后才发现了这个问题, 最终我将foo函数改为如下的形式之后, 运行结果就符合预期了:
const foo = async () => {
try {
const res1 = await p1();
return p2(res1);
} catch (error) {
return Promise.reject(error);
使用Promise.reject处理一下这个字符串, 那最终返回的就是rejected的Promise了
但结合上面的知识我们不难发现, 这个foo函数还有一个写法也能符合我们的预期:
const foo = async () => {
const res1 = await p1();
return p2(res1);
p1报错, async能捕获这个错误并直接返回, p2报错也不用担心, 因为我们显式地写了return关键字
一次请求多个接口渲染页面
这个私以为也是最常见的情形
比如这里有两个接口:
const api = {
fetch1(delay) {
return new Promise((resolve, reject) => {
setTimeout(
() => {
resolve({
data: 1,
success: 1,
msg: 'done'
delay
fetch2(delay) {
return new Promise((resolve, reject) => {
setTimeout(
() => {
resolve({
data: 2,
success: 1,
msg: 'done'
delay
每次请求都渲染页面
这里就会有些出入了, 首先我们来看看每次请求都渲染页面, 那么此时我们有两个设置数据的方法, 对应两个请求:
handleSetData1:
handleSetData1 = async () => {
const res = await api.fetch1(2000);
if(res.success) {
return true;
return Promise.reject(res.msg);
handleSetData2:
handleSetData2 = async () => {
const res = await api.fetch2(100);
if(res.success) {
return true;
return Promise.reject(res.msg);
为了模拟真实的请求, 这里分别给它们做了延迟处理. 然后还有一个统一的请求数据的方法:
getData:
getData = () => {
Promise.all([
this.handleSetData1(),
this.handleSetData2()
两个接口, 我们有各自的方法去请求并且做设置数据渲染页面的操作, 然后还有一个统一的getData的方法调用, 看似没有问题, 但却可能造成多次渲染的问题, 比如上述代码, 两个接口分别有1秒和100毫秒的延迟, 这会导致延迟更短的那个先执行, 然后渲染页面, 接着延迟长的后执行, 然后再次渲染页面, 造成多次渲染的问题, 但我们的目的是接口请求都回来之后统一渲染, 毕竟页面依赖两个接口的数据
理想情况下两个接口延迟非常之短, 那可能会只渲染一次, 但我们写代码, 不能寄希望于理想情况, 反而应该考虑一些边界问题, 比如这里的重复渲染问题, 既然需要请求完毕所有接口再渲染页面, 那么我们就改一改
全部请求完成再渲染页面
还是那两个api, 此时我们修改代码, 等它们都请求完毕之后再渲染页面:
getData:
getData = async () => {
const res = await Promise.all([
api.fetch1(2000),
api.fetch2(100)
const [ res1, res2 ] = res;
if(res1.success && res2.success) {
this.setState({
a: res1.data,
b: res2.data
这样就能减少一次额外的渲染
微信公众号权限验证配置
关于微信公众号开发相关的内容在这篇文章中有聊到, 需要的可以看一看: vue3+vant开发微信公众号网页爬坑不完全指北
handleWxConfig:
const handleWxConfig = (jsApiList) => {
return new Promise((resolve, reject) => {
.retrieveWxJsSdkConfig()
.then((res) => {
if (res.code === 0) {
wx.config({
debug: false,
appId: res.data.appId,
timestamp: res.data.timestamp,
nonceStr: res.data.nonceStr,
signature: res.data.signature,
jsApiList
wx.ready(function () {
resolve(true);
wx.error(function (res) {
reject(res);
} else {
reject(res.msg);
微信公众号选择图片并获取本地图片数据
同样在vue3+vant开发微信公众号网页爬坑不完全指北中有提到
handleWxChooseImg:
const handleWxChooseImg = () => {
return new Promise((resolve, reject) => {
wx.chooseImage({
count: 1,
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera'],
success(res) {
resolve(res);
fail(error) {
reject(error);
handleWxGetLocalImgData:
const handleWxGetLocalImgData = (localId) => {
return new Promise((resolve, reject) => {
wx.getLocalImgData({
localId,
success(res) {
resolve(res);
error(error) {
reject(error);
handleGetImgBase64:
const handleGetImgBase64 = async () => {
try {
const res1 = await handleWxChooseImg();
const res2 = await handleWxGetLocalImgData(res1.localIds[0]);
return res2;
} catch (error) {
return Promise.reject(error);
然后根据上面的知识, 不难发现我们还可以省略try...catch...语句写成这样:
const handleGetImgBase64 = async () => {
const res1 = await handleWxChooseImg();
const res2 = await handleWxGetLocalImgData(res1.localIds[0]);
return res2;
但私以为还是保留try...catch...语句比较好, 不然可能会产生歧义: 这要是报错了怎么办, 而有了try...catch...就一目了然了, 毕竟try...catch...表示对可能出现异常的语句做一个容错的处理, 代码也是先给人读, 然后顺带让机器执行的, 人要是阅读起来有障碍, 那机器执行得再顺畅也无济于事
好的, 这就是这篇文章的全部内容了, 欢迎大家在评论区和我一起交流探讨, 最后, 如果你觉得这篇文章写得还不错, 别忘了给我点个赞, 如果你觉得对你有帮助, 可以点个收藏, 以备不时之需, 想看更多知识干货欢迎关注哦~
参考文献:
Promise 对象
async 函数
async function