• JS异步操作新体验之 async函数


    1、初识 async 函数
     
    ES6中提供了两个很好的解决异步操作的方案 Promise 和 Generator,ES2017标准中引入的 async 函数就是建立在 Promise 和 Generator的基础之上,它是 Generator函数的语法糖,使异步操作更加方便
     
    先通过一个异步读取文件的小栗子来对比下Promise、Generator 和 async 的异同点
    const fs = require('fs')
    
    function readFile(fileName) {
      return new Promise((resolve, reject) => {
        fs.readFile(fileName, (err, data) => {
          if(err) {
            reject(err)
          }
          resolve(data.toString())
        })
      })
    }
    (1)、通过 Promise 读取文件
    readFile('data/a.txt').then(res => console.log(res))
                          .catch(err => console.log(err))
    
    readFile('data/b.txt').then(res => console.log(res))
                          .catch(err => console.log(err))
    
    readFile('data/c.txt').then(res => console.log(res))
                          .catch(err => console.log(err))
    (2)、通过 Generator 函数读取文件
    与 Promise 相比较,优点:把所有的接口都封装在一个函数里面了,缺点:代码量稍微多一点点
    function* gen() {
      yield readFile('data/a.txt')
      yield readFile('data/b.txt')
      yield readFile('data/c.txt')
    }
    
    let it = gen()
    
    it.next().value.then(res => {
      console.log(res)
    }).catch(err => {
      console.log(err)
    })
    
    it.next().value.then(res => {
      console.log(res)
    }).catch(err => {
      console.log(err)
    })
    
    it.next().value.then(res => {
      console.log(res)
    }).catch(err => {
      console.log(err)
    })
     
    (3)、通过 async 函数读取文件
    充分吸取了 Promise 和 Generator的优点,同时避免了它们的缺点
    async function read() {
      let readA = await readFile('data/a.txt')
      let readB = await readFile('data/b.txt')
      let readC = await readFile('data/c.txt')
    
      console.log(readA)
      console.log(readB)
      console.log(readC)
    }
    
    read()

    最终的输出结果

    通过上例可以看出,async 函数就是将 Generator函数的星号("*")替换成了 async,把 yield 替换成了 await
     
    async 函数对 Generator函数的改进,主要体现在三个方面:
     
    (1)、内置执行器
    async函数的执行,与普通函数一模一样,只需一行;而 Generator函数,需要调用next 方法
     
    (2)、返回值是 Promise
    async函数的返回值是Promise,这比 Generator函数返回一个 Iterator对象方便多了
     
    (3)、更好的语义化
    从字面意思上来讲,async是英文单词 asynchronous 的缩写,表示异步的;await中的 wait 是等待的意思。
     
    因此相比 Generator函数中的星号和yield,语义更加清楚,async 表示这是一个异步操作的函数,await 表示紧跟在后面的表达式需要等待结果
     
     
    2、async 函数的多种使用形式
    // 函数声明
    async function foo() {
      // ....
    }
    
    // 函数表达式
    let foo = async function() {
      // ....
    }
    
    // 箭头函数
    let foo = async() => {}
    
    // 对象的方法
    let obj = {
      name: 'Roger',
      async foo() {
    
      }
    }
    obj.foo().then(res => {
      // ....
    })
    
    // 类的方法
    class Student{
      constructor(name, age) {
        this.name = name
        this.age = age
      }
      async say() {
        return `My name is ${this.name}, I'm ${this.age} years old !`
      }
    }
    
    let jim = new Student('Jim Green', 13)
    jim.say().then(res => console.log(res))   // My name is Jim Green, I'm 13 years old !
    3、基本用法
     
    async 函数返回一个 Promise 实例对象,可以使用 then 方法添加回调函数。
     
    当函数执行时,一旦遇到 await 就会先返回,等到异步操作完成,再接着执行函数体内后面的语句
     
    // 休眠 ms 毫秒
    function sleep(ms) {
      return new Promise(resolve => {
        setTimeout(resolve, ms)
      })
    }
    
    async function print(ms) {
      console.log('start... ...')
      await sleep(ms)
      console.log('end... ...')
    }
    
    print(1000)

    (1)、async 函数内部 return语句返回的值,会成为then方法回调函数的参数
    async function foo() {
      return 'hello world'
    }
    
    foo().then(res => console.log(res))   // hello world
    (2)、async 函数内部抛出错误,会导致返回的 Promise对象变成reject状态,抛出的错误会被catch方法回调函数接收到
    async function bar() {
      return new Error('Error... ...')
    }
    
    bar().then(res => console.log(res))
         .catch(err => console.log(err))   // Error: Error... ...
    (3)、只有 async 函数内部的异步操作执行完,才会执行 then方法指定的回调函数
    async function baz() {
      await new Promise(resolve => {
        console.log('执行第一个异步操作')
        setTimeout(resolve, 2000)
      })
    
      await new Promise(resolve => {
        console.log('执行第二个异步操作')
        setTimeout(resolve, 3000)
      })
    
      return '异步执行完毕再执行then方法'
    }
    
    baz().then(res => {console.log(res)})

    4、await 命令
     
    await 用于等待一个 Promise对象,它只能在一个 async函数中使用
    [return_value] = await expression
    
    表达式:一个 Promise对象或者任何要等待的值
    
    返回值:返回 Promise对象的处理结果。如果等待的不是 Promise对象,则返回该值本身
    await命令会暂停当前 async函数的执行,等待 Promise处理完成。如果 Promise正常处理,其回调的 resolve函数参数会作为 await表达式的返回值,继续执行 async函数。如果 Promise处理异常,await表达式会把 Promise的异常原因抛出
     
    // 如果 await 命令后的表达式的值不是一个 Promise,则返回该值本身
    async function foo() {
      return await 123
    }
    
    foo().then(res => console.log(res))    // 123
    
    
    // 如果 Promise 正常处理(fulfilled),其回调的resolve函数参数作为 await 表达式的返回值
    async function bar() {
      let f = await new Promise((resolve, reject) => {
        resolve('我是表达式的返回值')
      })
      console.log(f)   // 我是表达式的返回值
    
      return 'ending'
    }
    
    bar().then(res => {console.log(res)})    // ending
    
    
    // 如果 Promise 处理异常(rejected),await 表达式会把 Promise 的异常原因抛出
    async function baz() {
      await new Promise((resolve, reject) => {
        reject(new Error('出错啦......'))
      })
    }
    
    baz().then(res => console.log(res))
         .catch(err => console.log(err))     // Error: 出错啦......
    (1)、任何一个 await语句后面的 Promise对象变为 reject状态,那么整个 async函数都会中断执行
     
    async function foo() {
      await Promise.reject('error')
      return 'ending'   // 未执行
    }
    
    foo().then(res => console.log(res))
    // Uncaught (in promise) error
    (2)、如果希望当前面的异步操作失败时,不要中断后面的异步操作,可以把前面的 await放在try...catch结构里面
     
    async function foo() {
      try{
        await Promise.reject('error')
      } catch(e) {
        
      }
      return await Promise.resolve('执行完毕')
    }
    
    foo().then(res => console.log(res))    // 执行完毕
    还可以在 await后面的 Promise对象再跟一个 catch方法,处理前面可能出现的错误
     
    async function foo() {
      await Promise.reject('error').catch(err => console.log(err))
      return '执行完毕'
    }
    
    foo().then(res => console.log(res))    // 执行完毕
    (3)、如果想让多个异步操作同时触发,缩短程序的执行时间,可以参考如下两种写法
     
    // 写法一
    let [foo, bar] = await Promise.all([getFoo(), getBar()]);
    
    // 写法二
    let fooPromise = getFoo();
    let barPromise = getBar();
    let foo = await fooPromise;
    let bar = await barPromise;
  • 相关阅读:
    24. Swap Nodes in Pairs
    23. Merge k Sorted Lists
    shell脚本报错:"[: =: unary operator expected"
    一种用 数组元素 指定 所调函数 的方法
    阻塞 非阻塞
    Linux open() 一个函数,两个函数原型
    QT 执行windows cmd 命令并读取结果
    Qt5 escape spaces in path
    获取磁盘的 总容量,空余容量,已用容量 【windows】
    通过进程名称,获取其路径
  • 原文地址:https://www.cnblogs.com/rogerwu/p/10784236.html
Copyright © 2020-2023  润新知