先说明一下:Promise 和 class 属于 ES6,async/await 严格来说是后续 ES2017 加入的,但现代 JavaScript 实战里通常会一起学。
10. Promise:处理异步任务
先理解为什么需要 Promise。
JavaScript 经常遇到这种事情:
js
调用接口
↓
等待后端返回
↓
拿到结果
↓
继续处理
但是接口请求不会立即完成。
比如:
js
const response = axios.get('/user/1')
这里 response 并不是后端真正返回的数据,因为请求还没完成。
所以 JavaScript 需要一种机制:
等异步操作完成以后,再执行后面的代码。
Promise 就是为这个设计的。
10.1 Promise 的三个状态
一个 Promise 有三个状态:
text
pending
进行中
fulfilled
成功
rejected
失败
整个过程只能:
text
pending
↓
fulfilled
或者:
text
pending
↓
rejected
一旦成功或者失败,就不会再改变。
10.2 最基础写法
js
const promise = new Promise((resolve, reject) => {
const success = true
if (success) {
resolve('操作成功')
} else {
reject('操作失败')
}
})
这里:
js
resolve(...)
表示:
成功。
而:
js
reject(...)
表示:
失败。
10.3 怎么接收 Promise 的结果
使用:
js
.then()
和:
js
.catch()
例如:
js
promise
.then(result => {
console.log(result)
})
.catch(error => {
console.log(error)
})
如果执行:
js
resolve('操作成功')
就进入:
js
.then()
如果:
js
reject('操作失败')
就进入:
js
.catch()
所以可以先记:
text
resolve
↓
then
reject
↓
catch
10.4 Axios 为什么可以 .then()
比如你平时写:
js
getUser(1).then(response => {
console.log(response.data)
})
之所以可以调用:
js
.then()
就是因为:
js
getUser(1)
返回的是一个 Promise。
例如若依里面经常:
js
export function getUser(userId) {
return request({
url: `/system/user/${userId}`,
method: 'get'
})
}
这里:
js
request(...)
本质上返回 Promise。
所以:
js
getUser(1)
也就返回 Promise。
因此你才能:
js
getUser(1).then(...)
10.5 Promise 链式调用
Promise 很重要的能力是:
js
.then()
.then()
.then()
例如:
js
getUser(1)
.then(response => {
return response.data
})
.then(user => {
return user.name
})
.then(name => {
console.log(name)
})
假设接口返回:
js
{
data: {
id: 1,
name: '张三'
}
}
执行过程:
text
response
↓
response.data
↓
user
↓
user.name
↓
张三
为什么可以继续 .then()?
因为 .then() 会返回一个新的 Promise。
10.6 return 特别重要
看看:
js
getUser(1)
.then(response => {
return response.data
})
.then(data => {
console.log(data)
})
这里:
js
return response.data
会把数据传给下一个:
js
.then(data => {})
如果你忘记:
js
getUser(1)
.then(response => {
response.data
})
.then(data => {
console.log(data)
})
那么:
js
data
就是:
js
undefined
所以 Promise 链里:
想把数据交给下一个
.then(),通常就要return。
10.7 返回另一个 Promise
这个更重要。
例如:
js
getUser(1)
.then(response => {
const userId = response.data.id
return getOrders(userId)
})
.then(response => {
console.log(response.data)
})
执行顺序:
text
先查询用户
↓
拿到 userId
↓
查询用户订单
↓
拿到订单
这里:
js
return getOrders(userId)
返回的是另一个 Promise。
JavaScript 会等这个 Promise 执行完成后,再进入下一个 .then()。
这就是 Promise 链。
10.8 catch
例如:
js
getUser(1)
.then(response => {
return response.data
})
.catch(error => {
console.error(error)
})
如果请求失败,或者 .then() 里面代码报错:
js
throw new Error('处理失败')
一般都会进入:
js
.catch()
10.9 finally
还有:
js
.finally()
不管成功还是失败都会执行。
例如 Loading:
js
this.loading = true
getUser(1)
.then(response => {
this.user = response.data
})
.catch(error => {
console.error(error)
})
.finally(() => {
this.loading = false
})
特别适合:
text
关闭 loading
释放资源
恢复按钮状态
11. async / await
async / await 是 Promise 的更现代写法。
重点:
它并不是另一套异步机制。
本质上还是 Promise。
只是让异步代码看起来更像同步代码。
11.1 Promise 写法
例如:
js
getUser(1).then(response => {
console.log(response.data)
})
使用 async/await:
js
const response = await getUser(1)
console.log(response.data)
11.2 await 是什么意思
js
const response = await getUser(1)
可以理解:
text
执行 getUser(1)
↓
它返回 Promise
↓
等待 Promise 成功
↓
把成功结果赋值给 response
所以:
js
const response = await getUser(1)
大致等价于:
js
getUser(1).then(response => {
})
11.3 await 一般必须放在 async 函数里
错误:
js
function getData() {
const response = await getUser(1)
}
正确:
js
async function getData() {
const response = await getUser(1)
}
或者:
js
const getData = async () => {
const response = await getUser(1)
}
11.4 Vue 实战
以前:
js
getList() {
listOrder(this.queryParams).then(response => {
this.orderList = response.rows
this.total = response.total
})
}
可以改成:
js
async getList() {
const response = await listOrder(this.queryParams)
this.orderList = response.rows
this.total = response.total
}
我个人更推荐第二种。
因为当业务逻辑复杂以后,可读性明显更好。
11.5 连续多个接口时区别特别明显
假设:
text
查询用户
↓
查询订单
↓
查询订单详情
Promise:
js
getUser(1)
.then(response => {
const userId = response.data.id
return getOrders(userId)
})
.then(response => {
const orderId = response.data[0].id
return getOrderDetail(orderId)
})
.then(response => {
console.log(response.data)
})
使用 async/await:
js
async function loadData() {
const userResponse = await getUser(1)
const userId = userResponse.data.id
const orderResponse = await getOrders(userId)
const orderId = orderResponse.data[0].id
const detailResponse = await getOrderDetail(orderId)
console.log(detailResponse.data)
}
这个就很像正常业务流程:
text
第一步
第二步
第三步
更容易维护。
11.6 async/await 怎么处理异常
Promise 使用:
js
.catch()
而 async/await 一般搭配:
js
try...catch
例如:
js
async function loadUser() {
try {
const response = await getUser(1)
console.log(response.data)
} catch (error) {
console.error(error)
}
}
对应关系:
text
Promise async/await
.then() await
.catch() try/catch
.finally() finally
例如:
js
async getList() {
this.loading = true
try {
const response = await listOrder(this.queryParams)
this.orderList = response.rows
this.total = response.total
} catch (error) {
console.error(error)
} finally {
this.loading = false
}
}
这是非常标准的实际项目写法。
11.7 一个非常重要的问题:await 是不是会卡死页面?
不会。
比如:
js
const response = await axios.get('/user')
并不是说:
JavaScript 整个线程什么都不干了。
而是当前这个:
js
async function
暂停向下执行。
等 Promise 完成以后继续。
页面其它事件、渲染等仍然可以执行。
这个理解很重要。
11.8 串行和并行
这是 await 实战里很重要的一点。
比如两个接口互不依赖:
js
const user = await getUser()
const config = await getConfig()
执行方式:
text
getUser
↓
等待完成
↓
getConfig
↓
等待完成
属于串行。
如果两个接口完全没有依赖,可以:
js
const [userResponse, configResponse] = await Promise.all([
getUser(),
getConfig()
])
执行方式:
text
getUser ───────→
getConfig ───────→
同时执行。
所以:
js
Promise.all()
也是非常实用的东西。
例如:
js
const [userRes, roleRes, deptRes] = await Promise.all([
getUser(userId),
getRoleList(),
getDeptList()
])
比依次等待:
js
const userRes = await getUser(userId)
const roleRes = await getRoleList()
const deptRes = await getDeptList()
更快。
前提是:
三个接口互不依赖。
12. class
ES6 增加了:
js
class
用来创建类。
JavaScript:
js
class User {
constructor(name) {
this.name = name
}
sayHello() {
console.log(`hello ${this.name}`)
}
}
使用:
js
const user = new User('张三')
user.sayHello()
输出:
text
hello 张三
12.1 constructor
js
constructor(name) {
this.name = name
}
你可以直接理解成 Java 的构造方法。
调用:
js
new User('张三')
的时候自动执行。
12.2 实例属性
js
class User {
constructor(name, age) {
this.name = name
this.age = age
}
}
创建:
js
const user = new User('张三', 20)
那么:
js
user.name
// 张三
user.age
// 20
12.3 实例方法
js
class User {
constructor(name) {
this.name = name
}
sayHello() {
console.log(`你好,我叫${this.name}`)
}
}
调用:
js
const user = new User('张三')
user.sayHello()
12.4 继承 extends
Java:
java
class Admin extends User {
}
JavaScript 非常相似:
js
class Admin extends User {
}
例如:
js
class User {
constructor(name) {
this.name = name
}
sayHello() {
console.log(`我是${this.name}`)
}
}
class Admin extends User {
deleteUser() {
console.log('删除用户')
}
}
使用:
js
const admin = new Admin('管理员')
admin.sayHello()
admin.deleteUser()
12.5 super
如果子类也有构造方法:
js
class Admin extends User {
constructor(name, role) {
super(name)
this.role = role
}
}
这里:
js
super(name)
就是调用父类构造方法。
class 在前端实际使用多不多?
有,但不像:
text
const
箭头函数
解构
数组方法
Promise
async/await
那么高频。
现在 Vue3 项目里,业务代码直接写 class 的情况并不算特别多。
但很多库和框架内部大量使用:
js
class
例如:
js
class ApiClient {
constructor(baseURL) {
this.baseURL = baseURL
}
get(url) {
// ...
}
post(url, data) {
// ...
}
}
所以:
要会看、会写基础 class,但不用把它当目前最重点。
把 10~12 串起来
来看一个实际一点的例子。
js
class UserService {
async getUserDetail(userId) {
try {
const response = await getUser(userId)
return response.data
} catch (error) {
console.error('查询用户失败', error)
throw error
}
}
}
这里包含:
第 12 个
js
class UserService
类。
第 11 个
js
async getUserDetail()
异步函数。
第 11 个
js
await getUser(userId)
等待 Promise。
第 10 个
js
getUser(userId)
本质返回 Promise。
第 3 个也可以加入
例如:
js
const { data } = await getUser(userId)
return data
这样你会发现 ES6 这些东西不是孤立的,而是在实际代码里混在一起使用。
目前 12 个知识点
现在整体结构已经很清楚了:
text
1. let / const
变量声明
2. 模板字符串
`${}`
3. 解构赋值
const { data } = response
4. 箭头函数
item => item.id
5. 对象增强
{ name, age }
6. 展开运算符
{ ...user }
7. 剩余参数
(...args)
8. 默认参数
pageNum = 1
9. 数组方法
map/filter/find/some/every
10. Promise
异步结果
11. async / await
更清晰地写 Promise
12. class
类和继承
其中如果按你现在的 Vue / JavaScript 实战重要程度,我会把这一组排序为:
text
★★★★★ async / await
★★★★★ Promise
★★★☆☆ class