1.asysc函数的含义
ES2017引入标准asysc函数,使得异步操作变得更加方便。
asysc是什么呢?他就是Generator函数的语法糖。
前面介绍过一个Generator函数,依次读取两个文件;
{
let fs = require('fs);
let readFile = function (fileName){
return new Promise((resolve,reject) => {
fs.readFile(fileName,(err,data) => {
if(error) return reject(error)
resolve(data);
})
})
}
let gen = function* (){
let f1 = yield readFile('/data1');
let f2 = yield readFile('/data2');
console.log(f1.toString());
console.log(f2.toString());
}
}
将上面的代码协程async函数的形式,具体如下:
let asyncReadFile = async function(){
let f1 = await readFile('/data1');
let f2 = await readFile('/data2');
console.log(f1.toString());
console.log(f2.toString());
}
1.2async函数对Generator函数的改进
-
1.内置执行器
Generator函数的执行必须依赖执行其,所以才有了co模块,而async函数自带执行其。也就是说,async函数的执行与普通函数一模一样,只要一行。
asyncReadFile();
上面的代码调用了ascyncReadFile函数,然后他就会自动执行,输出最终的结果。这完全不像Generator函数需要调用next方法或者使用co模块才能真正执行并地道道最终结果。 -
2.更好的语义
async和await比起星号和yield,语义更加清楚了。async表示函数里有异步操作,await表示进更在后面的表达式需要等待结果。 -
3.更广的适用性
co模块约定,yield命令后面只能是Thunk哈桑农户或者是Promise对象,而async函数的await命令后面,可以是Promise对象和原始类型的值(数值,字符串和布尔值,但这时等同于同步操作) -
4.返回值为Promise
async函数的返回值是Promise对象,这比Generator函数的返回值是Iterator对象方便了许多。可以用then放大之心下一步操作。
进一步说,async函数完全可以看作是由多个异步操作包装城的一个Promise对象,而await命令就是内部then命令的语法糖。
2.用法
async函数返回一个Promise对象,可以使用then方法添加回调函数,当函数执行额时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。
{
function timeout(ms){
return new Promise((resolve,reject) => {
setTimeout(resolve, ms);
})
}
async function asyncPrint(value, ms){
await timeout(ms);
console.log(value);
}
asyncPrint('hello world', 1000)
}
// 上面的代码指定50ms后输出hello world 。
// 由于async函数返回的是Promise对象,可以作为await命令的参数。所以上面的例子也可以写成下面的形式。
{
async function timeout(ms){
await new Promise((resolve) => {
setTimeout(resolve,ms)
})
}
async function asyncPrint(value, ms){
await timeout(ms);
console.log(value);
}
asyncPrint('hello world', 1000)
}
3.语法
-
函数声明
{
async function foo() {}
} -
函数表达式
{
const foo = async function() {}
} -
对象的方法
{
let obj = {
async foo() {};
}obj.foo().then(…)
} -
箭头函数
{
const foo = async () => {}
}
3.1 返回Promise对象
async函数内部返回一个Promise对象。
async函数内部return语句返回的值,会成为then方法回调函数的参数。
{
async function f(){
return 'hello world';
}
f().then( v => {
console.log(v);
//hello world
})
}
// 上面代码中,函数f内部return命令返回的值会被then方法回调函数接收到。
// async函数内部抛出错误会导致返回的Promsie对象变为reject状态。抛出的错误对象会被catch方法回调函数接收到。
{
async function f2(){
throw new Error('出错了!');
}
f2().then( v => {
console.log(v);
})
//Error: 出错了
}
3.2 Promise 对象状态变化
async函数返回的Promise对象必须等到内部所有的await命令后面的Promise对象执行完才会发生状态变化,除非遇到return语句或者抛出错误。也就是说,只有async内部的异步操作执行完,才会执行then方法指定的回调函数。
{
async function f(){
let result01 = await f2(100);
let result02 = await f2( result01 + 100);
let result03 = await f2( result02 + 100);
return result03;
}
function f2(wait){
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(wait)
},wait)
})
}
f().then( v => {
console.log(v);
})
// /300
}
3.3 await 命令
正常情况下,await命令后面是一个Promise对象。如果不是,会被转成一个立即resolve的Promise对象。
{
async function f() {
return await 123;
}
f().then(v => {
console.log(v);
//123
})
}
// await命令后面的Promise对象如果变为reject状态,则reject的参数会被catach方法的回调接收到。
{
async function f2(){
await Promise.reject('出错了');
}
f2()
.catch( e => {
console.log(e);
//出错了
})
}
// 只要有一个await语句后面的Promise状态变为reject那么整个async函数都会中断执行
{
async function f3(){
await Promise.reject('后面的不会执行');
await Promise.resolve('123');
return 456;
}
f3()
.then( v => {
console.log(v);
})
.catch(e => {
console.log(e);
//后面的不会执行
})
}
有时我们希望即使前一个异步操作失败,也不要中断后面的异步操作.这时可以将第一个await放在try...catch结构里面
这样不管这个异步操作是否成功,第二个await都会执行
{
async function f3(){
try {
await Promise.reject('后面的不会执行');
} catch(e){
}
await Promise.resolve(console.log(123));
}
f3()
.then( v => {
console.log(v);
})
}
另外一种方法时再后面的Promise对象后添加一个catch方法,处理前面可能出现的错误.
{
async function f3(){
await Promise.reject('后面的不会执行')
.catch( e=> {
console.log(e);
//后面的不会执行
});
return await Promise.resolve('123');
}
f3()
.then( v => {
console.log(v);
//123
})
}
3.4 错误处理
因为一旦await后面的Promise对象抛出错误对像,导致catch方法的回调函数被调用,它的参数就是抛出的错误对象.
为了防止这种错误,当我们有多个await命令的时候,可以将他们统一放在try…catch语句中.
{
async function main(){
try{
let a = await Promise.resolve(100);
let b = await Promise.reject(a + 100);
let c = await Promise.resolve(300);
return c;
} catch(err){
console.log(err);
}
}
main();
}
使用try...catch实现多次重复尝试
{
async function test(){
let i;
for(i =0; i< 100; i++){
try{
await Promise.rejecte();
break;
} catch(err){}
}
console.log(i);
}
test();
}
上面的代码中,如果await操作成功,则会break语句推出循环;如果失败,则会被catch语句捕捉,然后进入下一轮循环,直到成功为止.
3.5 使用注意点
-
1.前面已经说过,await命令后面的Promise对象的运行结果可能是rejected,所以最好把await命令放在try…catch代码块中.
-
2.多个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();
}
这两种写法中,getFoo和getBar都是同时触发,这样就会缩短程序的执行时间.
-
3.await命令只能用在async函数中,如果用在普通函数中就会报错.
-
参考文献 <ES6标准入门>