async/await 是 ES7 中被提实现异步操作的技术, 相对比较新, 好在 babel 爸爸已对它支持, 我们可以欢快的对它进行使用. 了解 async/await 前要求对 Promise 有一定的理解, 或可阅读 深入理解 Promise 一文. 盗图
# async - 定义异步函数
async 译: 异步, 是 Generator 函数的语法糖. 该函数会返回一个 promise 对象, 可以使用 then 方法添加回调函数, 如果在函数内直接 return,Async 会通过 Promise.resolve() 将其封装成 Promise() 对象, 也可以通过. then 添加回调函数
- async function pms() {
- return 'abc'
- }
- console.log(pms()) // [object Promise] { ... }
以上方法执行返回了一个 promise 对象, 其执行等效于
- async function pms() {
- return new Promise((resolve, reject) => {
- resolve('abc')
- })
- }
在没有配合 await 时, 我们可以调用 promise 的. then 方法得到执行结果. 当然也会有. catch 方法
- pms().then(res => {
- console.log(res) // 'abc'
- }).catch(e => {
- console.log('错误')
- })
- # await - 暂停异步函数的执行
await, 即: async wait, 旨在等待异步执行结束, 使用于 async 函数内部. 异步未执行结束, 阻塞当前代码. 嗯哼? 阻塞!!!
与线程阻塞不同的是, await 的阻塞发生在 async 函数内部, 可以理解为一个异步的阻塞. 跟在 await 后的 JS 表达式, 可以等待很多类型的事件, 但初衷是用于等待 Promise 对象. 如果 await 的对象是 promise 对象, 则阻塞异步函数代码执行等待 promise 的 resolve 状态, 如果是同步执行的表达式则立即执行.
- # async/await 的使用
- function asyncFunc(time) {
- return new Promise((resolve, reject) => {
- setTimeout(() => { // 用 setTimeout 模拟异步
- resolve('async result')
- }, time)
- })
- }
- function normalFunc() {
- console.log('normal result')
- }
- async function awaitDemo() {
- await normalFunc() // 执行立即打印 normal result
- const res = await asyncFunc(1000)
- console.log(res) // 执行 1s 后打印'async result'
- }
- awaitDemo() // 执行
以上的例子比较常规, 再看看下面这个例子
- async function func() {
- console.log('123')
- }
- async function run() {
- const res = await func()
- console.log(res) // 123
- }
- run()
该例子中, func 看似一个普通函数, 但经 async 定义后, 会返回一个 promise 对象, 此时的 await 等待的就是该 promise 对象的 resolve 参数, 即 123.
对比上一个例子中的 normalFunc, 主要有两个理解点
(1) 增加了 async 的普通函数变成了一个异步函数, await 等待的对象为 promise 对象, 返回 resolve 参数
(2)await 可以等待任何东西, 如果等待的是普通内容, 则直接返回该内容
# [填坑 1] 异常处理
细心的你应该已经发现, await 返回的是 promise 的 resolve 参数, 但对于 catch 却没有实际的处理. 那当我们的请求发生异常时, 该怎么办? 答案是使用 try-catch, 在 Promise 中的. catch() 分支会流入 catch 中
- function asyncFunc(time) {
- return new Promise((resolve, reject) => {
- setTimeout(() => { // 用 setTimeout 模拟异步
- reject('promise reject')
- }, time)
- })
- }
- // 为了处理 Promise.reject 的情况我们应该将代码块用 try catch 捕获一下
- async function awaitDemo() {
- try {
- const res = await asyncFunc(1000)
- console.log(res)
- } catch (err) {
- console.log(err.message || 'Uncatch Error')
- }
- }
- awaitDemo() // 如果没有使用 try-catch, 执行会报错
- # [填坑 2] 并行请求
对于一个真实的业务需求, 通常会有多个异步请求需要同时执行, 如获取左侧目录树, 和登录账户的用户信息等. 因 await 遇到异步会阻塞, 当一个 async 函数内有多个异步函数需要调用时, 就会出现相互等待的现象. 天哪,"异步" 变成了 "同步" 了, 这不是我们所希望的
- function asyncFunc() {
- return new Promise((resolve, reject) => {
- setTimeout(() => {
- resolve('request done')
- })
- })
- }
- async function bugDemo() {
- await asyncFunc()
- await asyncFunc()
- await asyncFunc()
- console.log('finish')
- }
以上代码正常执行了, 没有报出异常, 但仔细观察控制台 timeline 可以发现, 三个函数是同步顺序执行的, 其罪魁祸首就是 await 的等待机制. 那怎么解决这个问题?
其实 async-await 只是 promise 的语法糖, 但其并不能代替 Promise, 从对异常的处理上就可以看出, 还需要引入 try-catch , 本问题是另一个体现. 解决办法需要使用 promise 的. all()
promise.all( iterable ), 当所有请求都为 resolve 时, 返回 resolve 状态
- async function correctDemo() {
- const f1 = asyncFunc()
- const f2 = asyncFunc()
- const f3 = asyncFunc()
- await Promise.all([f1, f2, f3]);
- console.log('finish')
- }
- # [填坑 3] await 必须在 async 的上下文中
await 并不只是使用在 async 函数中即可, 还必须在 asyn 函数的上下文中
- // 虽在 async 函数里使用, 但在 forEach 上下文中, 异常
- async function errorDemo() {
- [1, 2, 3, 4, 5].forEach(item => {
- await item;
- });
- }
- errorDemo() // SyntaxError: await is only valid in async function
- // await 必须在 async 函数的上下文中
- async function correctDemo() {
- let arr = [1, 2, 3, 4, 5];
- for (let i = 0; i < arr.length; i ++) {
- await arr[i];
- }
- }
- # refs
async 函数介绍
异步神器 Async
来源: http://www.jianshu.com/p/e511be9c1280