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;

 

posted @ 2019-04-28 15:25  rogerwu  阅读(12741)  评论(0编辑  收藏  举报