ES6 -Async--Await异步编程的曙光

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标准入门>

发布了85 篇原创文章 · 获赞 16 · 访问量 6105

猜你喜欢

转载自blog.csdn.net/qq_43955202/article/details/104381040