30 Promise 及其语法糖 async 和 await
TS 在 1.6 版本实验性地支持了 async 函数。在过去的 JavaScript 当中,如果想保证代码的执行顺序,需要使用回调函数,当需要执行的步骤多了时就会陷入当说的“回调地狱”。自从 ES6 增加了 Promise 之后,状况有了缓解,我们先来看个例子,一个简单的多个 ajax 请求的例子:
每个人的生命都是一只小船,理想是小船的风帆。 ——张海迪
ajax.post(
// 这里你可以先忽略ajax的定义,他的post方法用来发送一个post请求
'/login', // 第一个参数时要请求的url
{
data: {
user_name: 'lison',
password: 'xxxxx',
},
}, // 第二个参数是这个请求要携带的参数
function (res) {
var user_id = res.data.user_id
ajax.post(
// 这里在/login接口成功返回数据后,再调用一个/user_roles接口,用来获取该登录用户的角色信息
'/user_roles',
{
data: {
user_id: user_id,
},
},
function (res) {
var role = res.data.role
console.log(role)
}
)
} // 第三个参数是接口响应之后的回调函数
)
在这个例子中,我们先调用登录的接口发送用户名和密码,然后服务端进行校验之后返回这个用户的一些信息,然后我们可以从信息中拿到用户 id 去获取它的角色用于权限控制。这个过程是有先后顺序的,必须先登录后获取角色,为了保证这个顺序,在过去要使用回调函数,当然一些库也支持链式调用。再来看下使用 ES6 的 Promise 需要怎么写:
const loginReq = ({ user_name, password }) => {
// 封装一个loginReq函数,用来返回一个Promise,用来调用/login接口
return new Promise((resolve, reject) => {
// Promise接收一个回调函数参数,这个函数有两个参数,两个参数都是回调函数
ajax.post(
'/login',
{
user_name,
password,
},
(res) => {
resolve(res) // 第一个参数resolve用来在执行成功后调用,传给他的参数,可以在这个promise的then函数参数中获取到
},
(error) => {
reject(error) // 第二个参数reject用来在执行出现错误后调用,传给他的错误信息,可以在这个promise的catch函数参数中获取到
}
)
})
}
const getRolesReq = ({ user_id }) => {
// 封装一个getRolesReq函数,用来返回一个Promise,用来调用/user_roles接口
return new Promise((resolve, reject) => {
ajax.post(
'/user_roles',
{
data: {
user_id,
},
},
(res) => {
resolve(res)
},
(error) => {
reject(error)
}
)
})
}
loginReq({ user_name: 'lison', password: 'xxxxx' }).then((res) => {
// 这里在调用loginReq函数后返回一个Promise,在内部当执行到resolve的地方时,这里的then的回调函数就会执行
getRolesReq({ user_id: res.data.user_id }).then((res) => {
console.log(res.data.role)
})
})
这看起来代码变长了,但是当我们搭配使用诸如 Axios 这类 ajax 请求库和 ES6 语法时,对于一些复用性高的接口调用能够起到很好的封装作用,而且使用起来较为简洁。
ES7 中增加了 async 和 await 的规范,它们其实是 Promise 的语法糖。TypeScript 在 1.6 支持了 async 和 await,下面我们通过 setTimeout 来实现异步过程,看下在 TypeScript 中如何使用 async 和 await:
interface Res {
// 我们定义一个接口,用来定义接口返回结果的结构
data: {
[key: string]: any
}
}
namespace axios {
// 现在我们来定义一个命名空间,用来模拟axios实现接口调用
export function post(url: string, config: object): Promise<Res> {
// 返回值类型是一个Promise,resolve传的参数的类型是Res
return new Promise((resolve, reject) => {
// 然后这里返回一个Promise
setTimeout(() => {
// 通过setTimeout实现异步效果
let res: Res = { data: {} }
if (url === '/login')
res.data.user_id = 111 // 我们这里通过简单判断,来模拟调用不同接口返回不同数据的效果
else res.data.role = 'admin'
console.log(2)
resolve(res) // 在这里传入res结果
}, 1000)
})
}
}
interface Info {
user_name: string
password: string
}
async function loginReq({ user_name, password }: Info) {
// 这里使用async关键字修饰这个函数,那么他内部就可以包含异步逻辑了
try {
console.log(1)
const res = await axios.post('/login', {
// 这里调用/login接口
data: {
user_name,
password,
},
})
console.log(3)
return res
} catch (error) {
throw new Error(error)
}
}
async function getRoleReq(user_id: number) {
try {
const res = await axios.post('/user_roles', {
data: {
user_id,
},
})
return res
} catch (error) {
throw new Error(error)
}
}
loginReq({ user_name: 'lison', password: '123' }).then((res) => {
const {
data: { user_id },
} = res
getRoleReq(user_id).then((res) => {
const {
data: { role },
} = res
console.log(role)
})
})
这个例子中用到了很多我们前面学习到的知识,可以帮大家进行复习和实践。
本节小结
本小节我们学习了如何使用 Promise 来保证异步代码的执行顺序,通常我们在调用多个接口,后面接口依赖前面接口返回的数据的时候会用到;还有就是比如 confirm 弹窗这种,需要在用户点击了“确定”或者“取消”之后才能执行一些逻辑,这种也适用。我们还学习了 Promise 的语法糖 async/await,使用这种语法更为形象清晰,但是不好的地方在于需要使用 try … catch 来获取原本 Promise 使用 catch 获取的错误信息。
本章到这里就结束了,下一章我们将学习《项目配置及书写声明文件》,更加偏实战了,所以前面这些基础知识一定要学扎实了,要经常复习运用下,不过我们后面的课程也会对前面部分知识进行巩固的。