背景
promise的出现对于异步编程是一个跨越式的提高,但是往往在实际业务中存在很多更加复杂的流程,promise还是无法满足我们的需要,这时候在ES7中提出了async函数
概念
async
函数是 Generator
函数的语法糖。使用 关键字 async
来表示,在函数内部使用 await
来表示异步,await关键字只能用在async定义的函数内。async
函数就是将 Generator 函数的星号(*
)替换成async
,将yield
替换成await
,对于Generator 不熟悉的可以移步Generator 函数
想较于 Generator,Async
函数的改进在于下面四点: 错误处理
- 内置执行器:Generator 函数的执行必须依靠执行器,而
Aysnc
函数自带执行器,调用方式跟普通函数的调用一样 - 更好的语义:
async
和await
相较于*
和yield
更加语义化 - 更广的适用性:
co
模块约定,yield
命令后面只能是 Thunk 函数或 Promise对象。而async
函数的await
命令后面则可以是 Promise 或者 原始类型的值(Number,string,boolean,但这时等同于同步操作) - 返回值是 Promise:
async
函数返回值是 Promise 对象,比 Generator 函数返回的 Iterator 对象方便,可以直接使用then()
方法进行调用
优势
对于promise的优势有哪些方面
1.语法简洁,同步代码执行异步操作
//promise写法
makeRequest().then((result) => {functiona()})
.then((result) => {functionb()})
.then((result) => {functionc()})
.then((result) => {functiond()})
....
// async
async makeRequest(){
await functiona()
await functionb()
await functionc()
await functiond()
}
2. 错误处理
promise中,try/catch不能处理promise内部的错误,因为promise对象抛出的错误不会传递到外层。所以捕获异常需要使用.catch,这样错误处理代码非常冗余。并且,在我们的实际生产代码会更加复杂。
//promise捕获错误
const makeRequest = () => {
try {
getJSON()
.then(result => {
// JSON.parse可能会出错
const data = JSON.parse(result)
console.log(data)
})
// 处理异步代码的错误
.catch((err) => {
console.log(err)
})
} catch (err) { //此处catch无效
console.log(err)
}
}
//async捕获错误
const makeRequest = async () => {
try {
// this parse may fail
const data = JSON.parse(await getJSON())
console.log(data)
} catch (err) {
console.log(err)
}
}
使用aync/await的话,可以捕获promise内部错误,这里有一点需要注意,当 async
函数中只要一个 await
出现 reject 状态,则后面的 await
都不会被执行。
async function f() {
await Promise.reject('出错了');
await Promise.resolve('hello world'); // 不会执行
}
所以要把第一个await
放在try...catch
结构里面,这样不管这个异步操作是否成功,第二个await
都会执行。但是在实际业务中有很多个await要处理呢
async function f() {
try {
await fetchDataA()
} catch(err) {
console.log('err is ->', err)
}
try {
await fetchDataB()
} catch(err) {
console.log('err is ->', err)
}
try {
await fetchDataC()
} catch(err) {
console.log('err is ->', err)
}
.....
}
相信这样的代码很难忍受,如果有多个await
命令,可以统一放在try...catch
结构中
async function main() {
try {
const val1 = await fetchDataA();
const val2 = await fetchDataB();
const val3 = await fetchDataC();
}
catch (err) {
console.error(err);
}
}
注意,如果多个await
命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。
//getFoo完成以后才会执行getBar,比较耗时
let foo = await getFoo();
let bar = await getBar();
// 不存在先后关系,可以同时触发
// 写法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);
// 写法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;
3.代码调试
const makeRequest = () => {
return callAPromise()
.then(() => callAPromise())
.then(() => callAPromise())
.then(() => callAPromise())
.then(() => callAPromise())
.then(() => {
throw new Error("oops");
})
}
makeRequest()
.catch(err => {
console.log(err);
// output
// Error: oops at callAPromise.then.then.then.then.then (index.js:8:13)
})
Promise链中返回的错误栈没有给出错误发生位置的线索。更糟糕的是,它会误导我们;错误栈中唯一的函数名为callAPromise,然而它和错误没有关系。(文件名和行号还是有用的)。然后当你想打断点去具体调试的时候,会发现不能在返回表达式的箭头函数中设置断点
const makeRequest = async () => {
await callAPromise()
await callAPromise()
await callAPromise()
await callAPromise()
await callAPromise()
throw new Error("oops");
}
makeRequest()
.catch(err => {
console.log(err);
// output
// Error: oops at makeRequest (index.js:7:9)
})
async/await中的错误栈会指向错误所在的函数,并且可以自行设置断点进入具体的函数内部去观察代码运行,这一点在分析生产环境的错误日志时会非常有用
4.依赖于之前异步函数的处理
比如调用promise1,使用promise1返回的结果去调用promise2,然后使用两者的结果去调用promise3。
//代码可读性差
const makeRequest = () => {
return promise1()
.then(value1 => {
return Promise.all([value1, promise2(value1)])
})
.then(([value1, value2]) => {
return promise3(value1, value2)
})
}
//使用async/await的话,代码会变得异常简单和直观。
const makeRequest = async () => {
const value1 = await promise1()
const value2 = await promise2(value1)
return promise3(value1, value2)
}
5.条件判断
比如需要获取数据,然后根据返回数据决定是直接返回,还是继续获取更多的数据。
//promise
const makeRequest = () => {
return getJSON()
.then(data => {
if (data.needsAnotherRequest) {
return makeAnotherRequest(data)
.then(moreData => {
console.log(moreData)
return moreData
})
} else {
console.log(data)
return data
}
})
}
//async/await
const makeRequest = async () => {
const data = await getJSON()
if (data.needsAnotherRequest) {
const moreData = await makeAnotherRequest(data);
console.log(moreData)
return moreData
} else {
console.log(data)
return data
}
}