其他章节请看:

es6 快速入门 系列

async

前文我们已经知道 promise 是一种异步编程的选择。而 async 是一种用于执行异步任务更简单的语法。

Tip:建议学完 Promise 在看本文。

async 函数

async 函数是使用 async 关键字声明的函数。就像这样:

async function fa(){

}

async 函数可以看作由多个异步操作包装成的一个 Promise 对象。

async 函数返回 Promise

async 函数总是返回一个 Promise 对象。如果一个 async 函数的返回值看起来不是 promise,那么它将会被隐式地包装在一个 promise 中。请看示例:

async function fa() {
return 1
} // 等价于 function fa() {
return Promise.resolve(1)
} console.log( fa() instanceof Promise) // true

即使 async 方法中没有显示的 return ,async 方法仍会返回 Promise。请看示例:

async function fa() {}
console.log( fa() instanceof Promise)

fa 方法等价于:

function fa() {
return Promise.resolve()
}

async 函数多种形式

async 函数有多种使用形式。例如:

// 函数表达式
const fa = async funciton() {}; // 对象的方法
let obj = {async foo(){}} // Class 的方法
class Dog{
async say(){}
} // 箭头函数
const fa = async () => {}

形式虽然很多,但都是在函数前面增加 async 关键字。

async 函数中的 return

async 函数内的 return 返回值,会成为 then() 方法回调函数的参数。请看示例:

async function foo() {
return 'hello'
} foo().then(v => {
console.log(v)
}) // hello

async 函数内部抛出的错误会导致返回的 Promise 对象变为 reject 状态。抛出的错误对象会被 catch 方法回调接收到。请看示例:

async function foo() {
throw new Error('fail')
return 'hello'
} foo().catch(v => {
console.log(v.message)
}) // fail

Promise 对象的状态变化

async 函数返回的 Promise 对象必须等到内部所有 await 命令后的 Promise 对象执行完才会发生状态变化,除非遇到 return 语句,或者抛出错误才会立刻结束。请看示例:

function createPromise(val, time = 1000){
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(val)
resolve(val)
}, time)
})
} async function foo() {
let a = await createPromise(1)
let b = await createPromise(2)
return {a, b}
} foo().then(v => {
console.log(v)
}, v => {
console.log(v.message)
}) /*
1
2
{ a: 1, b: 2 }
*/

这段代码需要 2 秒,等待内部两个 Promise 状态都置为已完成,才会输出 { a: 1, b: 2 }

如果遇到 return 或者抛出错误,则会立即结束。就像这样:

async function foo() {
// 遇到 return
return 1
// 或抛出错误
// throw new Error('fail')
let a = await createPromise(1)
let b = await createPromise(2)
return {a, b}
}

await

asyn 函数可能包含 0 个或多个 await 表达式。就像这样:

async function fa() {
return await 1
}

await 表达式会暂停整个 async 函数的执行进程并出让其控制权,只有当其等待的基于 promise 的异步操作被兑现或被拒绝之后才会恢复进程。promise 的解决值会被当作该 await 表达式的返回值。

await 的返回值

首先看一段代码:

async function fa() {
const result = await 1
return result
} fa().then(v => {
console.log(v)
}) // 1

为什么 result 是 1?

首先,因为 await 命令后面是一个 Promise 对象。如果不是,会被转为一个立即 resolve 的 Promise 对象。所以下面 fa() 方法是相等的:

async function fa() {
return await 1
} // 等价于 async function fa() {
return await Promise.resolve(1)
}

而在 Promise 中所学,我们知道 fa() 方法又等于如下代码:

async function foo() {
return await new Promise((resolve, reject) => {
resolve(1)
})
}

其次,Promise 的解决值会被当作该 await 表达式的返回值。所以 result 等于 1。

如果删除 fa() 方法中的 return,将输出 undefined。请看示例:

async function fa() {
// 删除 return
await 1
} fa().then(v => {
console.log(v)
}) // undefined

reject 中断 async 函数

await 命令后的 Promise 对象如果变成 reject 状态,则 reject 的参数会被 catch 方法回调函数接收。就像这样:

async function foo() {
await Promise.reject(1) // {1}
} foo().then(v => {
console.log(v)
}).catch(v => {
console.log(`catch, ${v}`)
}) // catch, 1

请注意,await 语句(行{1})前面没有 return 语句,但是 reject() 方法的参数依然传入了 catch 方法的回调函数中,这点与 resolve 状态不相同。

只要一个 await 语句后面的 Promise 变成 reject,那么整个 async 函数都会中断。请看示例:

async function foo() {
await Promise.reject(1) await new Promise((resolve, reject) => {
console.log(2)
resolve()
})
return 3
} foo().then(v => {
console.log(v)
}).catch(v => {
console.log(`catch, ${v}`)
}) // catch, 1

由于第一个 await 后面的 Promise 变成 reject,整个 async 函数就中断执行。

如果我们希望前一个异步操作失败,也不中断后面的异步操作,可以这么写:

try{
await Promise.reject(1)
}catch(e){ } // 亦或者
// 在 Promise 一文中提到拒绝处理程序能恢复整条链的执行
await Promise.reject(1).catch(() => {})
...

如果 await 后面的异步操作出错,那么等同于 async 函数返回的 Promise 对象被 reject。就像这样:

async function foo() {
await new Promise((resolve, reject) => {
throw new Error('fail')
})
return 3
} foo().then(v => {
console.log(v)
}).catch(v => {
console.log(`catch, ${v}`)
}) // catch, Error: fail

防止出错的方法也是将其放在 try ... catch 方法中。下面例子使用 try...catch 实现多次尝试:

function request(v){
return new Promise((resolve, reject) => {
if(v == 2){
console.log(`resolve${v}`)
resolve(v)
}else{
console.log(`fail${v}`)
throw new Error('fail')
}
})
} async function foo() {
for(let i = 0; i < 5; i++){
try{
await request(i)
break
}catch(e){} // {1}
}
return 'end'
} foo().then(v => {
console.log(v)
}).catch(v => {
console.log(`catch, ${v}`)
}) // fail0 fail1 resolve2 end

这段代码,如果 await 操作成功,则会使用 break 语句退出循环;如果失败,则会被 catch(行{1}) 捕获,然后进入下一轮循环。

await 与并行

下面的代码,会依次输出 1 和 2,属于串行。

function createPromise(val, time = 1000){
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(val)
resolve(val)
}, time)
})
} async function foo() {
let a = await createPromise(1)
let b = await createPromise(2)
} foo() // 1 2

如果多个异步操作不存在继发关系,最好让它们同时触发。将 foo() 方法改为下面任一方式:

// 方式一
async function foo() {
let p1 = createPromise(1)
let p2 = createPromise(2)
// 至此,两个异步操作都已经发出
await p1
await p2
} // 方式二
async function foo() {
let [p1, p2] = await Promise.all([createPromise(1), createPromise(2)])
}

再次运行,只需要 1 秒就会同时输出 1 2。

async 函数中的 await

await 关键字只能用在 async 函数中。请看示例:

async function fa(){
let arr = [1, 2, 3] arr.forEach(v => {
await v
})
}
// SyntaxError: await is only valid in async function

这段代码将报语法错误。

如果将 forEach 方法的参数改为 async 函数,就像这样:

function createPromise(val, time = 1000){
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(val)
resolve(val)
}, time)
})
} async function fa(){
let arr = [1, 2, 3]
// 改为 async 函数
arr.forEach(async v => {
await createPromise(v)
})
} fa() // 1 2 3

等待 1 秒后同时输出 1 2 3。因为这 3 个异步操作是并发执行。

如果希望多个请求并发执行,也可以使用 Promise.all 方法。就像这样:

// 替换 fa() 方法即可
async function fa(){
let arr = [1, 2, 3]
let promises = arr.map(v => createPromise(v))
let results = await Promise.all(promises)
console.log(results)
}

而如果需要继发,可以采用 for 循环:

// 替换 fa() 方法即可
async function fa(){
let arr = [1, 2, 3]
// 将 forEach 改为 for 循环
for(let i = 0; i < arr.length; i++){
await createPromise(arr[i])
}
}

每过一秒,会依次输出 1 2 3。

其他章节请看:

es6 快速入门 系列

最新文章

  1. 使用parted给大于2T的磁盘分区
  2. 将ECSHOP会员注册页面的Email修改成非必填项
  3. 30天,O2O速成攻略【8.16武汉站】
  4. 自定义View绘制字符串
  5. QTP自动化测试权威指南 连载(一)
  6. 比较好的自学IT的网站
  7. iOS 音频开发经验汇总
  8. [android]android开发中的运行错误之:adb.exe
  9. 【剑指offer】Q40:数组中出现一次的数
  10. [笔记]cin、cout与scanf、printf的效率差异对比分析
  11. python实战===使用smtp发送邮件的源代码,解决554错误码的问题,更新版!
  12. U3D 控件
  13. MySQL所学所思所想
  14. 使用 TRESTClient 與 TRESTRequest 作為 HTTP Client 之二 (POST 檔案)
  15. 我为什么推荐Prettier来统一代码风格
  16. 细说shiro之一:shiro简介
  17. 剑指Offer 32. 把数组排成最小的数 (数组)
  18. 测试开发之前端——No9.HTML5中的视频/音频
  19. UI基础七:给普通其他界面的PRODUCT 添加标准的搜索帮助
  20. (笔记)CANOpen移植(CanFestival移植)

热门文章

  1. CAS的理解
  2. [Linux] 删除find到的目录
  3. 搞清楚 硬件环境 os环境 网络环境 搞清楚测试工具 测试步骤 自己搭测试环境 自测
  4. mysqldMY-010457] [Server] --initialize specified but the data directory has files in it. Aborting. 2020-12
  5. 有没有一种组合字体,中文是宋体,英文是times new roman?
  6. openstack创建vlan网络并配置网络设备
  7. Linux进阶之LAMP和LNMP动态网站搭建
  8. 9.4-6 kill &amp; killall &amp; pkill
  9. 《Matlab实用案例》系列Matlab从入门到精通实用100例案例教程目录(持续更新)
  10. Octave Convolution卷积