JS Advance --- async await

265 阅读4分钟

处理异步请求的方式

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

在日常开发,我们经常需要发生一个异步请求后,拿到异步请求的结果以后,在通过这个结果再去另一个接口中进行查找对应的值

// 需求: 
// 1> url: exmaple -> res: example
// 2> url: res + "aaa" -> res: example-aaa
// 3> url: res + "bbb" => res: exmaple-aaa-bbb

多次回调

// 使用定时器模拟请求
function request(url) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (url) {
        resolve(url)
      } else {
        reject('url is not defined')
      }
    }, 1000)
  })
}

request('example').then(res => {
  request(`${res}-aaa`).then(res => {
    request(`${res}-bbb`).then(res => {
      console.log(res)
    })
  })
})

I3XnBu.png

这种解决方案是最简单也是最直接的

但是如果我们需要的异步请求过多的时候,就很容易出现上图的回调地狱

这对于后期的维护和扩展是非常不利的

Promise中then的返回值

// 使用定时器模拟请求
function request(url) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (url) {
        resolve(url)
      } else {
        reject('url is not defined')
      }
    }, 1000)
  })
}

// 这种解决方式是将回调函数的嵌套变为了同层调用
// 但是依旧没有从根本上解决多次回调产生的不利于维护和扩展的问题
request('example').then(res => request(`${res}-aaa`))
.then(res => request(`${res}-bbb`))
.then(res => console.log(res))

Promise + generator

这种解决方式可以使用同步的方式去编写异步代码

async + await出现之前,这是最好的解决方式

// 使用定时器模拟请求
function request(url) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (url) {
        resolve(url)
      } else {
        reject('url is not defined')
      }
    }, 1000)
  })
}

function* fetchData() {
  let res = yield request('example')
  res = yield request(`${res}-aaa`)
  res = yield request(`${res}-bbb`)
  console.log(res)
}

// 执行generator函数
function execGenerator(generatorFn) {
  const generator = generatorFn()

  function exec(result) {
    const { done, value: promise } = generator.next(result)

    if (done) {
      return result
    }

    promise.then(res => {
      exec(res)
    })
  }

  exec()
}

execGenerator(fetchData)

在实际使用的时候,存在第三方库co可以主动帮助我们来执行generator函数,而不需要我们手动编写对应的execGenerator

# 安装
$ npm i co
// 使用定时器模拟请求
function request(url) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (url) {
        resolve(url)
      } else {
        reject('url is not defined')
      }
    }, 1000)
  })
}

function* fetchData() {
  let res = yield request('example')
  res = yield request(`${res}-aaa`)
  res = yield request(`${res}-bbb`)
  console.log(res)
}

const co = require('co')

co(fetchData)

async + await

async+await是ES8中引入的关键字,是目前实际开发中,实际用来解决异步处理请求的方式

可以认为async+await在进行解析的时候,依旧会被转换为Promise+Generator的方式

async+await仅仅只是Promise+Generator的一种语法糖形式

function request(url) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (url) {
        resolve(url)
      } else {
        reject('url is not defined')
      }
    }, 1000)
  })
}

async function fetchData() {
  let res = await request('example')
  res = await request(`${res}-aaa`)
  res = await request(`${res}-bbb`)
  console.log(res)
}


fetchData()

async函数的基本定义方式

async关键字用于声明一个异步函数

async function foo() {}

const bar = async() => {}

class Person {
  async baz() {   
  }
}

异步函数执行流程

异步函数的内部代码执行过程和普通的函数是一致的,默认情况下也是会被同步执行

async function foo() {
  console.log('foo中的脚本')
}

console.log('script started')
foo()
console.log('script endedd')

/*
  =>
    script started
    foo中的脚本
    script endedd
*/

异步函数和普通函数的区别

区别一: 异步函数也可以有返回值,但是异步函数的返回值会被包裹到Promise.resolve中

// 就算异步函数中声明都不写
// 默认的返回值也是一个promise,只不过是Promise.resolve(undefined)
async function foo() {
}

const promise = foo()
promise.then(res => console.log('异步函数返回值:', res))
async function foo() {
  return 123
}

const promise = foo()
promise.then(res => console.log(res)) // => 123
async function foo() {
  // 如果异步函数的返回值是一个thenable对象
  // 那么会自动执行返回的thenable对象以便于获取最终的值
  return {
    then(resolve, reject) {
      resolve(123)
    }
  }
}

const promise = foo()
promise.then(res => console.log(res)) // => 123
async function foo() {
  return new Promise(resolve => resolve(123))
}

const promise = foo()
promise.then(res => console.log(res)) // => 123

区别二: 异步函数中抛出了异常,那么程序它并不会像普通函数一样报错,而是会作为Promise的reject来传递

async function foo() {
  throw new Error('something error in async function')
}

const promise = foo()
promise.catch(e => console.log(e))

区别三: 可以在异步函数内部使用await关键字,而在普通函数中是不可以的

function request() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(123)
    }, 1000)
  })
}

async function foo() {
  // await是后面会跟上一个表达式,这个表达式会返回一个Promise
  // 但await后边返回的promise的状态转变为resolved的时候。
  // 其结果就会作为await表达式的返回值的值赋值给res
  const res = await request()

  // await表达式后面的逻辑会等待await表达式对应的异步代码执行完毕以后再进行执行
  // 可以认为await表达式后边的逻辑代码其实就是需要写在await表达式返回的promise的then方法中的逻辑代码
  console.log(res)
}

foo()
// 如果await后面是一个普通的值,那么会直接返回这个值
function request() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(123)
    }, 1000)
  })
}

async function foo() {
  // 如果await后边跟上的是一个基本数据类型
  // 那么就等价于 Promise.resolve(123)
  const res = await 123

  console.log(res) // => 123
}

foo()

如果await后面的表达式,返回的Promise是resolve的状态,那么会将这个reject结果直接作为函数的Promise的resolve值;

function request() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(123)
    }, 1000)
  })
}

async function foo() {
  const res = await {
    then(resolve) {
      resolve(123)
    }
  }

  console.log(res) // => 123
}

foo()
function request() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(123)
    }, 1000)
  })
}

async function foo() {
  const res = await new Promise(resolve =>  resolve(123))

  console.log(res) // => 123
}

foo()

如果await后面的表达式,返回的Promise是reject的状态,那么会将这个reject结果直接作为函数的Promise的 reject值;

function request() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject(123)
    }, 1000)
  })
}

async function foo() {
  try {
    const res = await request()
    console.log(res)
  } catch(err) {
    console.log('err', err)
  }
}

foo()