zoukankan      html  css  js  c++  java
  • ES6

    1.含义

    async函数简洁点说就是Generator函数的语法糖。

    示例:一个读取文件的异步操作,逐步执行,使用Generator函数执行

     1     const fs = require('fs')
     2 
     3     const readFile = function (fileName) {
     4         return new Promise(function (resolve, reject) {
     5             fs.readFile(fileName, function (error, data) {
     6                 if (error) return reject(error);
     7                 resolve(data);
     8             });
     9         })
    10     }
    11 
    12     const gen = function* (){
    13         const f1 = yield readFile('./a')
    14         const f2 = yield readFile('./b')
    15         console.log("文件读取结束");
    16     }

     使用async函数执行

    1     const gen = async function (){
    2         const f1 = await readFile('./a')
    3         const f2 = await readFile('./b')
    4         console.log("文件读取结束");
    5     }

    可以看到,async函数就是将Generate函数的星号(*)替换成async,将yield替换为await而已。

    async函数对Generator函数的改进体现在以下四点:

    • 内置执行器:async函数的执行,与普通函数一模一样,只要一行
      • asyncReadFile();

         上面代码调用了asyncReadFile函数,然后就会自动执行,输出最后结果。

    • 更好的语义:
      • async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。
    • 更广的适用性:
      • Generator 函数的执行必须靠执行器,所以才有了co模块。
      • co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)
    • 返回值是Promise
    • async函数的返回值是Promise对象(Generator函数返回值是Iterator对象)。可以使用then方法指定下一步操作。

    2.基本用法

     async函数返回的是一个Promise对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体后面的语句:

     1     function p1(val) {
     2         return new Promise((resolve, reject) => {
     3             setTimeout(() => {
     4                 resolve("p1传递数据:" + val)
     5             }, 3000)
     6         })
     7     }
     8 
     9     function p2(val) {
    10         return new Promise((resolve, reject) => {
    11             setTimeout(() => {
    12                 resolve("p2传递数据:" + val)
    13             }, 2000)
    14         })
    15     }
    16 
    17     async function asyncTest(value) {
    18         const vp1 = await p1(value)
    19         const vp2 = await p2(vp1)
    20         return vp2
    21     }
    22 
    23     asyncTest("张三").then(res => {
    24         console.log(res)  // p2传递数据:p1传递数据:张三
    25     })

     上面代码中,p1,p2是两个异步操作。在asyncTest函数中现后调用了p1和p2,p2接收了p1的返回值作为参数。在5s以后输出了相应的值。

    async函数的使用形式:

    • 函数声明式:
      • async function foo() {}
    •  函数表达式:
      • const foo = async function () {}
    • 对象的方法:
      • let obj = { async foo() {} }
        obj.foo().then(...)
    • class的方法:
      • class Storage {
          constructor() {
            this.cachePromise = caches.open('avatars');
          }
        
          async getAvatar(name) {
            const cache = await this.cachePromise;
            return cache.match(`/avatars/${name}.jpg`);
          }
        }
        
        const storage = new Storage();
        storage.getAvatar('jake').then(…)
    • 箭头函数:
      • const foo = async () => {}

     3.语法

    async函数的语法规则总体上比较见到那,难点是错误处理机制。

    (1)返回Promise对象

    •  async函数内部return语句返回的值,会成为then方法回调函数的参数
      • async function f() {
          return 'hello world';
        }
        
        f().then(v => console.log(v))
        // "hello world"
    • async函数内部抛出错误,会导致返回的 Promise 对象变为reject状态。抛出的错误对象会被catch方法回调函数接收到。
      • async function f() {
          throw new Error('出错了');
        }
        
        f().then(
          v => console.log('resolve', v),
          e => console.log('reject', e)
        )
        //reject Error: 出错了

    (2)Promise对象状态的变化

      async函数返回的Promise对象,必须等到内部所有的await命令后面的Promise对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误。也就是说,只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。

     (3)await命令

    正常情况下,await命令后面是一个Promise对象,返回该对象的姐u共。如果不是Promise对象,就会直接返回对应的值

    async function f() {
      // 等同于
      // return 123;
      return await 123;
    }
    
    f().then(v => console.log(v))
    // 123

    另一种情况是,await命令后面是一个thenable对象(即定义了then方法的对象),那么await会将其等同于 Promise 对象。

    class Sleep {
        constructor(timeout){
            this.timeout = timeout
        }
    
        then(resolve,reject){
            const startTime = Date.now();
            setTimeout(()=>{
                resolve(Date.now() - startTime)
            },this.timeout)
        }
    }
    
    (async ()=>{
        const sleepTime = await new Sleep(1000);
        console.log(sleepTime);
    })()  // 1000

    上面代码中,await命令后面是一个Sleep对象的实例。这个实例不是Promise对象,但是因为定义了then方法,await会将其视为Promise处理。

    比如实现一个简单的程序休眠

    function sleep(interval){
        return new Promise((resolve,reject)=>{
            setTimeout(resolve,interval)
        })
    }
    
    async function one2Async(){
        for(let i = 1;i<=5;i++){
            console.log(i);
            await sleep(1000)
        }
    }
    
    one2Async();

     上面代码将会每隔1s输出一个i。不过需要注意的是,这里的await后面的promise对象仍然属于微任务队列的内容,如果在调用了one2Async方法后面继续添加语句,这语句仍然属于主线程(调用栈),会优先执行。如果需要在所有的都执行结束再执行期望的语句,还是需要放在then方法中执行:

    one2Async().then(() => {
            console.log("1111");
        })

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

        async function f() {
            await Promise.reject('出错了');
        }
    
        f()
            .then(v => console.log("vvv:", v))
            .catch(e => console.log("eee:", e))
        // eee:出错了

    注意,上面代码中,await语句前面没有return,但是reject方法的参数依然传入了catch方法的回调函数。这里如果在await前面加上return,效果是一样的。

    任何一个await语句后面的Promise对象变成reject状态,那么整个async函数都会中断执行。

    async function f() {
      await Promise.reject('出错了');
      await Promise.resolve('hello world'); // 不会执行
    }

    但是有时候,我们希望即使前一个异步操作失败,呀不要中断后面的异步操作。这时可以将第一个await放在try...catch结构里面。

        async function f() {
            try{
                await Promise.reject('出错了')
            }catch(e){
                return await Promise.resolve('hello world')
            }
        }
        f().then(v => console.log(v))  // hello world

    另一种方法是await后面的Promise对象再跟一个catch方法,处理前面可能出现的错误。

        async function f() {
            await Promise.reject('出错了')
                .catch(e => console.log(e));
                return await Promise.resolve('hello world')
        }
        f().then(v => console.log(v))
        // 出错了
        // hello world

    (4)错误处理

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

    ①使用Promise对象的catch捕获错误。

    async function f() {
      await new Promise(function (resolve, reject) {
        throw new Error('出错了');
      });
    }
    
    f()
    .then(v => console.log(v))
    .catch(e => console.log(e))
    // Error:出错了

    ②将其放在try.catch代码块当中

    async function f() {
      try {
        await new Promise(function (resolve, reject) {
          throw new Error('出错了');
        });
      } catch(e) {
      }
      return await('hello world');
    }

    如果有多个await命令,也是将其放在try...catch代码块当中。

    async function main() {
      try {
        const val1 = await firstStep();
        const val2 = await secondStep(val1);
        const val3 = await thirdStep(val1, val2);
    
        console.log('Final: ', val3);
      }
      catch (err) {
        console.error(err);
      }
    }

    4.使用注意点

    (1)把await命令放在try...catch代码块中

    上面说了,await后面的Promise对象,运行结果可能是reject,所以最好把await命令放在try...catch代码块中。

    async function myFunction() {
      try {
        await somethingThatReturnsAPromise();
      } catch (err) {
        console.log(err);
      }
    }
    
    // 另一种写法
    
    async function myFunction() {
      await somethingThatReturnsAPromise()
      .catch(function (err) {
        console.log(err);
      });
    }

    (2)多个await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。

    如:

    let foo = await getFoo()
    let bar = await getBar()

    上面代码中,getFoo和getBar是两个独立的异步操作,被写成了继发关系,这样比较耗时,因为只有前面完成以后,才会执行getBar,完全可以让它们同时触发。所以可以改成:

    // 写法一
    let [foo,bar] = await Promise.all([getFoo(),getBar()])
    
    // 写法二
    let fooPromise = getFoo()
    let barPromise = getBar()
    let foo = await fooPromise
    let bar = await barPromise 

    这样,getFoo和getBar都是同时触发,可以缩短程序执行时间

    示例:两个异步都是1s出结果:

    function p1f (val){
        return new Promise((resolve,reject) => {
            setTimeout(()=>{
                console.log("p1执行结束");
                resolve("p1执行结束")
            },1000)
        })
    }
    
    function p2f (val){
        return new Promise((resolve,reject) => {
            setTimeout(()=>{
                console.log("p2执行结束");
                resolve("p2执行结束")
            },1000)
        })
    }
    
     async function astncTest(){
        let [p1,p2] = await Promise.all([p1f(),p2f()])
         return p1 + "------" + p2
     }
    
     astncTest().then(res=>{
         console.log(res);
     })
    // p1执行结束
    // p2执行结束
    // p1执行结束------p2执行结束

    上面代码中,1s后p1f()和p2f()的结果同时输出。

    (3)await命令只能用在async函数之中,如果用在普通函数,就会报错

    如:

    async function dbFuc(db) {
      let docs = [{}, {}, {}];
    
      // 报错
      docs.forEach(function (doc) {
        await db.post(doc);
      });
    }

    此时如果将forEach方法的参数也改成async函数也可能报错,这里改成一个简单的异步:

    let a =[1,2,3,4,5]
    
    function testAsy(){
        a.forEach(async function(e){
            await new Promise((resolve,reject)=>{
                setTimeout(()=>{
                    console.log(e);
              resolve() },
    3000) }) }) } testAsy() // 1 // 2 // 3 // 4 // 5

    上面代码执行后在3s后统一输出了1-5,并非一个一个打印的。正确的写法是采用for循环:

        let a = [1, 2, 3, 4, 5]
    
        async function testAsy() {
            for (let e of a) {
                await new Promise((resolve, reject) => {
                    setTimeout(() => {
                        console.log(e);
                        resolve()
                    }, 1000)
                })
            }
        }
    
        testAsy()
    // 1
    // 2
    // 3
    // 4
    // 5

    这样就能实现每隔1s打印一个数字

    另一种方法是使用数组的reduce()方法

        let a = [1, 2, 3, 4, 5]
    
        async function testAsy() {
    
            await a.reduce(async (_,item) => {
                console.log('---',item);
                await _;
                await new Promise((resolve, reject) => {
                    setTimeout(() => {
                        console.log(item);
                        resolve()
                    }, 1000)
                })
            },undefined)
        }
    
        testAsy()

      上面例子中,reduce()方法的第一个参数是async函数,导致该函数的第一个参数是前一步操作返回的 Promise 对象,所以必须使用await等待它操作结束。另外,reduce()方法返回的是docs数组最后一个成员的async函数的执行结果,也是一个 Promise 对象,导致在它前面也必须加上await

      上面的reduce()的参数函数里面没有return语句,原因是这个函数的主要目的是db.post()操作,不是返回值。而且async函数不管有没有return语句,总是返回一个 Promise 对象,所以这里的return是不必要的。

     如果确实希望多个请求并发执行,可以使用Promise.all方法。当三个请求都会resolved时,下面两种写法效果相同。

    async function dbFuc(db) {
      let docs = [{}, {}, {}];
      let promises = docs.map((doc) => db.post(doc));
    
      let results = await Promise.all(promises);
      console.log(results);
    }
    
    // 或者使用下面的写法
    
    async function dbFuc(db) {
      let docs = [{}, {}, {}];
      let promises = docs.map((doc) => db.post(doc));
    
      let results = [];
      for (let promise of promises) {
        results.push(await promise);
      }
      console.log(results);
    }

     (4)async函数可以保留运行堆栈

    const a = () => {
      b().then(() => c());
    };

     上面代码中,函数a内部运行了一个异步任务b()。当b()运行结束的时候函数a不会中断,而是继续执行。等到b()运行结束,可能a()早就运行结束了,b()所在的上下文环境已经消失了,如果b()或者c()报错,错误堆栈将不包括a()。

    将其改为async函数:

    const a = async () => {
        await b();
        c();
    }

    上面代码中,b()运行的时候,a()时暂停执行,上下文环境都保存着。一旦b()或c()报错,错误堆栈将包含a()

  • 相关阅读:
    redis 安装配置
    ORM 效率补充
    function*
    路由器
    nodejs开发辅助工具nodemon
    npm淘宝镜像配置
    Node中的模块系统
    artTemplate不仅可以在浏览器中使用,还可以在node中使用
    代码风格JavaScript standard style与Airbnb style
    osChina.net工具
  • 原文地址:https://www.cnblogs.com/codexlx/p/14386314.html
Copyright © 2011-2022 走看看