title: async/await
categories: Javascript
tag:

  • async
    date: 2021-12-01 05:16:34

异步函数 async

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

  • async 是 asynchronous 单词的缩写,异步、非同步;
  • sync 是 synchronous 单词的缩写,同步、同时;

async 异步函数可以有很多中写法:

24_asyncawait - 图1

异步函数的执行流程

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

  1. async function foo() {
  2. console.log('执行foo')
  3. }
  4. console.log('111')
  5. foo()
  6. console.log('222')
  7. // 运行结果:
  8. // 111
  9. // 执行foo
  10. // 222

异步函数有返回值时,和普通函数会有区别:

  1. 情况一:异步函数也可以有返回值,但是异步函数的返回值会被包裹到 Promise.resolve 中;
  2. 情况二:如果我们的异步函数的返回值是 Promise,Promise.resolve 的状态会由 Promise 决定;
  3. 情况三:如果我们的异步函数的返回值是一个对象并且实现了 thenable,那么会由对象的 then 方法来决定;

如果我们在 async 中抛出了异常,那么程序它并不会像普通函数一样报错,而是会作为 Promise 的 reject 来传递;

await 关键字

async 函数另外一个特殊之处就是可以在它内部使用 await 关键字,而普通函数中是不可以的。

await 关键字有什么特点呢?

  • 通常使用 await 是后面会跟上一个表达式,这个表达式会返回一个 Promise;
  • 那么 await 会等到 Promise 的状态变成 fulfilled 状态,之后继续执行异步函数;
  1. function requestData() {
  2. return new Promise((resolve, reject) => {
  3. setTimeout(() => {
  4. resolve('123')
  5. }, 2000)
  6. })
  7. }
  8. async function foo() {
  9. const res = await requestData()
  10. console.log('后面的代码', res)
  11. }
  12. foo()

如果 await 后面是一个普通的值,那么会直接返回这个值;

  1. async function bar() {
  2. const res = await 123
  3. console.log('后面的代码', res)
  4. }
  5. bar()

如果 await 后面是一个 thenable 的对象,那么会根据对象的 then 方法调用来决定后续的值;

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

  1. async function foo() {
  2. const res = await requestData()
  3. console.log('后面的代码', res)
  4. }
  5. foo().catch((err) => {
  6. console.log(err)
  7. })

错误处理的方案

开发中我们会封装一些工具函数,封装之后给别人使用:

  • 在其他人使用的过程中,可能会传递一些参数;
  • 对于函数来说,需要对这些参数进行验证,否则可能得到的是我们不想要的结果;

很多时候我们可能验证到不是希望得到的参数时,就会直接 return:

  • 但是 return 存在很大的弊端:调用者不知道是因为函数内部没有正常执行,还是执行结果就是一个 undefined;
  • 事实上,正确的做法应该是如果没有通过某些验证,那么应该让外界知道函数内部报错了;

如何可以让一个函数告知外界自己内部出现了错误呢?

  • 通过 throw 关键字,抛出一个异常;
    throw 语句:
  • throw 语句用于抛出一个用户自定义的异常;
  • 当遇到 throw 语句时,当前的函数执行会被停止(throw 后面的语句不会执行);

如果我们执行代码,就会报错,拿到错误信息的时候我们可以及时的去修正代码。

throw 关键字

throw 表达式就是在 throw 后面可以跟上一个表达式来表示具体的异常信息:

throw 关键字可以跟上哪些类型呢?

  • 基本数据类型:比如 number、string、Boolean
  • 对象类型:对象类型可以包含更多的信息

Error 类型

事实上,JavaScript 已经给我们提供了一个 Error 类,我们可以直接创建这个类的对象:

  1. function foo() {
  2. console.log('1')
  3. throw new Error('error message')
  4. }
  5. foo()

Error 包含三个属性:

  • messsage:创建 Error 对象时传入的 message;
  • name:Error 的名称,通常和类的名称一致;
  • stack:整个 Error 的错误信息,包括函数的调用栈,当我们直接打印 Error 对象时,打印的就是 stack;

Error 有一些自己的子类:

  • RangeError:下标值越界时使用的错误类型;
  • SyntaxError:解析语法错误时使用的错误类型;
  • TypeError:出现类型错误时,使用的错误类型;

异常的处理

我们会发现在之前的代码中,一个函数抛出了异常,调用它的时候程序会被强制终止:

  • 这是因为如果我们在调用一个函数时,这个函数抛出了异常,但是我们并没有对这个异常进行处理,那么这个异常会继续传递到上一个函数调用中;
  • 而如果到了最顶层(全局)的代码中依然没有对这个异常的处理代码,这个时候就会报错并且终止程序的运行;

我们先来看一下这段代码的异常传递过程:

  • foo 函数在被执行时会抛出异常,也就是我们的 bar 函数会拿到这个异常;
  • 但是 bar 函数并没有对这个异常进行处理,那么这个异常就会被继续传递到调用 bar 函数的函数,也就是 test 函数;
  • 但是 test 函数依然没有处理,就会继续传递到我们的全局代码逻辑中;
  • 依然没有被处理,这个时候程序会终止执行,后续代码都不会再执行了;
  1. function foo() {
  2. throw new Error('error')
  3. }
  4. function bar() {
  5. foo()
  6. }
  7. function test() {
  8. bar()
  9. }
  10. test()
  11. console.log('test后续代码')

异常的捕获

但是很多情况下当出现异常时,我们并不希望程序直接推出,而是希望可以正确的处理异常:

  • 这个时候我们就可以使用 try catch
  1. function foo() {
  2. throw new Error('error')
  3. }
  4. function bar() {
  5. try {
  6. foo()
  7. } catch (err) {
  8. console.log(err.message)
  9. } finally {
  10. console.log('finally')
  11. }
  12. }
  13. function test() {
  14. bar()
  15. }
  16. test()
  17. console.log('test后续代码')

在 ES10(ES2019)中,catch 后面绑定的 error 可以省略。

当然,如果有一些必须要执行的代码,我们可以使用 finally 来执行:

  • finally 表示最终一定会被执行的代码结构;
  • 注意:如果 try 和 finally 中都有返回值,那么会使用 finally 当中的返回值;