含义
async函数,使得异步变得更方便。async函数是什么?一句话就是 Generator的语法糖。
用 Generator 依次读取两个文件
var fs = require('fs')
var readFile = functiton(filename) {
return new Promise(function (resolve,reject) {
fs.readFile(fileName, function(error, data) {
if(error) return reject(error)
resolve(data)
})
})
}
var gen = function* (){
var f1 = yield readFile('/1')
var f2 = yield readFile('/2')
console.log(f1.toString())
console.log(f2.toString())
}
复制代码
将上面写成 async 函数
var asyncReadFile = async function(){
var f1 = await readFile('/1')
var f2 = await readFile('/2')
console.log(f1.toString())
console.log(f2.toString())
}
复制代码
通过比较,async 函数就是将 Generator 函数的 * 替换成了 async ,将yield 题替换成 await。仅此而已。
用法
async 函数 返回一个Promise 对象,可以使用then添加回调,当函数执行的时候,一旦遇到 await 就会先返回,等到异步完成,再接着执行函数体后面的语句。
指定多少毫秒输出一个值
function timeout(ms){
return new Promise((resolve)=>{
setTimeout(resolve,ms)
})
}
async function asyncPrint(value,ms) {
await timeout(ms)
cosnole.log(value)
}
asyncPrint('hello world',50)
复制代码
写成这种方式也可以
async function timeout(ms){
await new Promise(resolve =>{
setTimeout(resolve,ms)
})
}
async function asyncPrint(value,ms){
await timeout(ms)
console.log(value)
}
复制代码
async 函数的多种使用形式
//函数声明
async function foo(){}
// 函数表达式
const foo = async function(){}
// 对象方法
let obj = {async foo() {}}
obj.foo.then()
// class 方法
class Storage {
constructor(){
this.catchPromise = caches.open('avatars')
}
async getAvatar(name) {
cosnt catch = await this.catchPromise;
return catch.match(`/avatars/${name}.jpg`)
}
}
const storage = new Storage()
storage.getAvatar('jack').then(...)
复制代码
语法
返回 Promise 对象
async 函数 返回一个 Promise 对象。
async 内部 return 语句返回的值,会成为 then 方法回调的参数
async function f(){
return 'hello world'
}
f().then(v=>{
console.log(v) // hello world
})
复制代码
async 函数内部抛出错误会导致返回的Promise对象变为 rejected 状态。抛出的错误对象 会被 catch方法回调函数接收到。
Promise 对象状态变化
async 函数返回Promise对象必须等到内部所有的 await 命令后边Promise 对象执行完才会发生状态改变,除非遇到 return 或者 抛出错误。只有 async 函数内部所有的异步操作执行完,才会执行 then 方法指定的回调函数。
await 命令
正常情况下, await 之后应该是一个 Promise 对象。如果不是,会被转化为一个立即resolve 的Promise。
async function f(){
return await 123;
}
f().then(v => console.log(v)) //123
复制代码
await 后面的Promise 变为 rejected,reject参数会被catch到
async function f(){
await Promise.reject('error')
}
f()
.then(v => console.log(v))
.catch(e => console.log(e)) // error
复制代码
只要有一个await语句后面的Promise变为 reject,整个async函数都会中断执行。 有时,我们那希望前面一个异步操作失败,也不要中断后面的异步操作。这时可以将第一个await 放在 try catch 结构里面,这样不管这个异步是不是成功,第二个都会执行。 或者 在await的后面Promise对象后添加一个 catch方法,处理前面可能出现的错误。
错误处理
如果 await 后面异步操作出错,那么等于 async 函数返回的 Promise 对象被 reject。
防止出错的方法也是将其放在 try...catch 代码块中。
async function f(){
try {
await new Promise((resolve,reject) => {
throw new Error("出错了")
})
} catch(e){
}
return await('hello world')
}
复制代码
如果有多个await,可以统一放在 try catch 代码块中
async function f(){
try {
var val1 = await ...
var val2 = await ...
var val3 = await ...
console.log('final',val3)
} catch(e){
console.log(e)
}
}
复制代码
使用 try catch 实现多次重复尝试
async function test(){
let i
for(i = 0;i<3;++i){
try{
await ...
break
} catch(err){}
}
console.log(i) // 3
}
复制代码
使用注意点
第一点:await命令后面的Promise对象的结果kennel rejected,所以,最好把await放在 try catch代码块中。
第二点:多个 await 命令后面的异步如果不存在 继发关系,最好让他们同时触发。
let foo = await getFoo()
let bar = await getBar()
复制代码
上面代码中,getFoo 和 getBar 是两个独立的异步(互不依赖)被写成继发关系。这样比较耗时,因为只有getFoo完成以后才会执行 getBar,完全可以让他们同时触发。
// 写法 1
let [foo,bar] = await Promise.all([getFoo(),getBar()])
// 写法 2
let fooPromise = getFoo()
let barPromise = getBar()
let foo = await fooPromise
let bar = await barPromise
复制代码
async 函数的实现原理
async 函数实现原理就是将Generator函数和自动执行器包装在一个函数里。
async function fn(){
}
// 等同于
function fn(args){
return spawn(function* (){
})
}
复制代码
其他异步处理方法的比较
async Promise Generator进行比较
假定某个DOM元素上面,部署了一系列动画,前一个结束,后一个才开始。如果有一个动画出错,就不再执行,返回上一个成功执行的动画返回值。
首先是 Promise 写法
function chainAnimationsPromise(el,animations){
// ret 保存上一个动画返回值
var ret = null
// 新建一个Promise
var p = new Promise.resolve()
// 使用then添加所有动画
for(var anim of animations){
p = p.then(function(val){
ret = val
return anim(elem)
})
}
return p.catch(function(e){
return ret
})
}
复制代码
代码完全是Promise的API,不容易看出语义
下面是Generator写法
function chainAnimationsGenerator(elem, animations) {
return spawn(function*(){
var ret = null
try {
for(var anim of animations){
ret = yield anim(elem)
}
} catch(e){
}
return ret
})
}
复制代码
这个写法的问题在于必须有一个任务运行器自动执行Generator函数,必须保证yield语句后面的表达式返回一个Promise。
最后是 async 写法
async function chainAnimationAsync(elem,animations){
var ret = null
try {
for(var anim of animations) {
ret = await anim(elem)
}
}catch(e){
}
return rett
}
复制代码
实例:按顺序完成异步操作
依次远程读取一组URL,按照读取顺序输出结果
promise
function loginOrder(urls){
// 远程读取所有url
const textPromises = urls.map(url => {
return fetch(url).then(res => res.text())
})
//按顺序输出
textPromises.reduce((chain,textPromise) => {
return chain.then(() => textPromise)
.then(text => console.log(txt))
},Promise.resolve())
}
复制代码
async
async function loginOrder(urls){
for(const url of urls){
const response = await fetch(url)
console.log(await response.text())
}
}
复制代码
以上写法是继发的,效率很低,我们需要同时发出远程请求。
async function loginOrder(urls){
const textPromises = urls.map(async url => {
const response = await fetch(url)
return response.text()
})
for(const textPromise of textPromises) {
console.log(await textPromise)
}
}
复制代码
以上代码,虽然map的参数是async函数,但是,他是并发执行的,只有async内部是继发执行,外部是不受影响的。后面的for of 内部使用了await,因此实现了按顺序输出。