一、前置知识
语法和基础用法:深入解析Promise与async/await的核心区别
二、在项目中使用
- 封装请求时:
// @/utils/request.js
import axios from "axios";
export function requestFn(options) {
return axios({
method: options.method || "get",
url: options.url,
baseURL: import.env.BASE_API,
headers: options.headers,
timeout: 200000,
data: options.data || "",
params: options.params || "",
responseType: options.responseType || "",
})
.then((response) => {
const res = response.data;
if (res.status !== 200) {
return Promise.reject(res.message);
// 或者 throw new Error(res.message);
}
// 在then里return普通值,会自动包装成 Promise.resolve,所以没必要手写Promise.resolve(res)
return res;
})
.catch((error) => {
throw error;
});
}
- 封装业务函数:
import { requestFn } from "@/utils/request";
// 调用:requestFn 得到一个Promise实例并返回
export const getAllSwiper = () => requestFn({ url: API.getAllSwiper });
- 在组件.vue 中调用业务函数:
// async/await 调用(推荐)
async yeWuFn1 () {
try {
const res = await getAllSwiper();
this.imageUrl = res.data;
} catch (err) {
}
};
// 也可用Promise的链式调用(.then/.catch),但多层then嵌套易形成回调地狱
yeWuFn1 () {
getAllSwiper()
.then((res) => {
this.imageUrl = res.data;
})
.catch((err) => {});
};
// 回调地狱
a().then(res => {
b().then(res => {
c().then(...)
})
})
// 正确链式调用
a()
.then(() => b())
.then(() => c())
三、async函数和.then()的返回值
已知async/await 是基于 Promise 的语法糖
1、async函数返回promise对象
即使
async foo() {}
也等价于:
function foo() {
return Promise.resolve(undefined)
}
❌ 错误写法1:
async getsfxFn() {
getsfxDetail().then(res => {
this.sfxList = res.data
})
}
export function getsfxDetail() {
return requestFn({
url: "xxx",
}).then((res) => {
return res;
});
}
这里getsfxFn虽然是async函数,但其中没有显式return一个promise,所以等价于:
async getsfxFn() {
getsfxDetail().then(res => {
this.sfxList = res.data
})
return Promise.resolve(undefined)
}
// 此时
console.log(await getsfxFn()) 为 undefined
❌ 错误写法2:
getsfxFn() {
getsfxDetail().then(res => {
this.sfxList = res.data
})
}
普通函数默认返回 undefined,使用 await getsfxFn()时,await完全无效
✅ 正确写法
// 写法1:这里getsfxFn是普通函数,返回必须是promise对象才能使用await
getsfxFn() {
return getsfxDetail().then(res => {
this.sfxList = res.data
})
}
// 或者写法2(推荐)
async getsfxFn() {
const res = await getsfxDetail()
this.sfxList = res.data
// 这里async函数会自动 return Promise.resolve(undefined)
}
// 调用:
await getsfxFn()
2、p.then()返回一个新的promise
为了支持promise的链式调用,必须返回新promise,promiseObj.then() !== promiseObj
所以就算.then() 里显式return普通值,也会自动进行包装成promise
上面举例这两种正确写法都:
- 返回一个 Promise
- 等待 getsfxDetail 完成
- 外部可以 await 它
- 执行顺序一致
为什么等价?
因为正确写法1中:getsfxFn() 返回的是 then 返回的 newPromise
实际相当于:
const promiseObj = getsfxDetail()
const newPromiseObj = promiseObj.then(res => {
this.sfxList = res.data
})
// 这个newPromiseObj是一个先 pending 后 fulfilled(undefined) 的 Promise,最终resolve(undefined)
return newPromiseObj
四、❗new Promise 常见 3 大坑
- 踩坑1:多此一举 + 风险翻倍的反模式:Promise 包 Promise
- 踩坑2:忘记 resolve/reject
- 踩坑3:executor 里同步抛错会自动 reject,但executor的异步回调里的抛错,如果不处理,会导致 pending:
executor(执行器函数):
new Promise(executor)
executor = function(resolve, reject) {}executor 的特点:
① 创建 Promise 时立即执行(同步执行)
console.log("A") new Promise(() => { console.log("B") }) console.log("C") 依次输出:A->B->C② 只能自动捕获同步异常
executor 里同步抛错会自动 reject:
new Promise(() => { throw Error("boom") }) 等价于: new Promise((resolve, reject) => { reject(Error("boom")) })③ 不能自动捕获executor 内的异步异常
executor的异步回调里的抛错,需要手动调用 resolve/reject,否则会导致 pending:
new Promise((resolve, reject) => { setTimeout(() => { throw Error("boom") }) }) // 因为没有手动调用 resolve/reject,所以同样会 pending —— 即使没抛错 new Promise((resolve, reject) => { setTimeout(() => { console.log(1) }) }) //根本原因是异步回调setTimeout 已经脱离 Promise executor 同步作用域一般是这么几种:
当前Promise实例接着进行 then/catch 回调里的异常,then 回调里的异常属于then 返回的新 Promise,不是原 Promise
当前Promise的executor内的异步异常,比如定时器或者调用接口
事件回调里的异常
举例:
// 能自动捕获: new Promise(() => { throw Error() }) // 不能自动捕获:这个异常属于 then 链上的 Promise,不是外层 Promise new Promise(resolve => { resolve() }).then(() => { throw Error() })
实战举例说明:
在项目里封装请求方法时:
因为axios 本身就已经返回 Promise,默认有 timeout/错误处理
- 不推荐
new Promise((resolve, reject) => {
somePromise.then(resolve).catch(reject);
});
如果非要这么写,那么要确保 requestFn()得到的Promise 不会出现永远 pending的情况
❗ 注意: new Promise 写法 = 依赖你手动调用 resolve/reject
所以要求:
✔ 有 timeout
✔ 有 .catch
✔ 所有if else 分支都有 resolve/reject
进行如下封装:
export function requestFn(options) {
return new Promise((resolve, reject) => {
axios({
method: options.method || "get",
url: options.url,
baseURL: store.state.baseUrl,
headers: options.headers,
timeout: 200000, // 设置请求超时时间
data: options.data || "",
params: options.params || "",
responseType: options.responseType || "",
})
.then((response) => {
if (response.status === 200 || response.status === 204) {
const res = response.data;
if (res.code === 200) {
resolve(res);
} else {
reject(res);
}
} else {
reject(response.data);
}
})
.catch((error) => {
reject(error);
});
});
}
❗千万注意在使用的时候,不要在业务层再 new Promise了!!
比如:
getList() {
return new Promise((resolve, reject) => {
requestFn({
url: "xxx",
params: {},
}).then((res) => {
const data = res?.data || {}
this.bookdirObj = this.changeBookdir(data.bookdir)
resolve(data)
}).catch((err) => {
resolve({})
});
})
}
- 一旦
const bookdirObj = this.changeBookdir(bookdir)这里报错:- → resolve/reject 都没执行,导致Promise 的 pending 状态没有变为 fulfilled/rejected 状态
- 那么getList()得到一个永远pending的Promise
这个异常是requestFn 返回的 Promise 链.then 回调里的同步异常,发生在 new Promise 的 executor 触发的异步链中
不属于new Promise的executor 里的同步抛错,所以 new Promise 没有捕获
✔ 改成:
getList() {
return requestFn({
url: "xxx",
params: {},
}).then((res) => {
const data = res?.data || {}
this.bookdirObj = this.changeBookdir(data.bookdir)
return data
}).catch((err) => ({}));
}
这种写法不会 pending 的原因:这里没有new Promise
requestFn()返回的Promise实例,接着进行then 回调,那么Promise 链的规则:
- then回调里抛错:
- → Promise 自动 rejected
- → 进入 catch
- → 返回 {}:普通值{},会自动包装成 Promise resolve({})返回
- getList()得到resolve({})
✔ 更推荐用async/await:
async getList() {
try {
const res = await requestFn({...})
const data = res?.data || {}
try {
this.bookdirObj = this.changeBookdir(data.bookdir)
} catch(e) {
this.bookdirObj = null
}
return data
} catch(e) {
return {}
}
}
pending 那咋了?
❗危害1:用
Promise.all的时候必须注意:只要有某个 Promise 永远 pending,会导致Promise.all卡死而永远等待,影响后续代码执行❗危害2:如果 pending Promise 被长期引用,可能导致相关闭包变量无法释放,从而产生内存占用增长
❗危害3:loading 状态永远不结束
loading = true await somePendingPromise loading = false // 永远执行不到
五、resolve(res)和promise.resolve(res)的区别
resolve(res)只能在 executor 里写:用于把当前这个 Promise变成 fulfilled
new Promise((resolve) => {
resolve(res)
})
Promise.resolve(res)创建一个新的已完成 Promise
new Promise((resolve) => {
resolve(123)
})
// 非严格意义上可以看做等价于:
Promise.resolve(123)
============分割线============
写到最后几乎不认识Promise了,感觉我仍然处于表面理解的状态,后续碰到了其它情况再来补充