title: async/await
categories: Javascript
tag:
- async
date: 2021-12-01 05:16:34
异步函数 async
async 关键字用于声明一个异步函数:
- async 是 asynchronous 单词的缩写,异步、非同步;
- sync 是 synchronous 单词的缩写,同步、同时;
async 异步函数可以有很多中写法:
异步函数的执行流程
异步函数的内部代码执行过程和普通的函数是一致的,默认情况下也是会被同步执行。
async function foo() {
console.log('执行foo')
}
console.log('111')
foo()
console.log('222')
// 运行结果:
// 111
// 执行foo
// 222
异步函数有返回值时,和普通函数会有区别:
- 情况一:异步函数也可以有返回值,但是异步函数的返回值会被包裹到 Promise.resolve 中;
- 情况二:如果我们的异步函数的返回值是 Promise,Promise.resolve 的状态会由 Promise 决定;
- 情况三:如果我们的异步函数的返回值是一个对象并且实现了 thenable,那么会由对象的 then 方法来决定;
如果我们在 async 中抛出了异常,那么程序它并不会像普通函数一样报错,而是会作为 Promise 的 reject 来传递;
await 关键字
async 函数另外一个特殊之处就是可以在它内部使用 await 关键字,而普通函数中是不可以的。
await 关键字有什么特点呢?
- 通常使用 await 是后面会跟上一个表达式,这个表达式会返回一个 Promise;
- 那么 await 会等到 Promise 的状态变成 fulfilled 状态,之后继续执行异步函数;
function requestData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('123')
}, 2000)
})
}
async function foo() {
const res = await requestData()
console.log('后面的代码', res)
}
foo()
如果 await 后面是一个普通的值,那么会直接返回这个值;
async function bar() {
const res = await 123
console.log('后面的代码', res)
}
bar()
如果 await 后面是一个 thenable 的对象,那么会根据对象的 then 方法调用来决定后续的值;
如果 await 后面的表达式,返回的 Promise 是 reject 的状态,那么会将这个 reject 结果直接作为函数的 Promise 的 reject 值;
async function foo() {
const res = await requestData()
console.log('后面的代码', res)
}
foo().catch((err) => {
console.log(err)
})
错误处理的方案
开发中我们会封装一些工具函数,封装之后给别人使用:
- 在其他人使用的过程中,可能会传递一些参数;
- 对于函数来说,需要对这些参数进行验证,否则可能得到的是我们不想要的结果;
很多时候我们可能验证到不是希望得到的参数时,就会直接 return:
- 但是 return 存在很大的弊端:调用者不知道是因为函数内部没有正常执行,还是执行结果就是一个 undefined;
- 事实上,正确的做法应该是如果没有通过某些验证,那么应该让外界知道函数内部报错了;
如何可以让一个函数告知外界自己内部出现了错误呢?
- 通过 throw 关键字,抛出一个异常;
throw 语句: - throw 语句用于抛出一个用户自定义的异常;
- 当遇到 throw 语句时,当前的函数执行会被停止(throw 后面的语句不会执行);
如果我们执行代码,就会报错,拿到错误信息的时候我们可以及时的去修正代码。
throw 关键字
throw 表达式就是在 throw 后面可以跟上一个表达式来表示具体的异常信息:
throw 关键字可以跟上哪些类型呢?
- 基本数据类型:比如 number、string、Boolean
- 对象类型:对象类型可以包含更多的信息
Error 类型
事实上,JavaScript 已经给我们提供了一个 Error 类,我们可以直接创建这个类的对象:
function foo() {
console.log('1')
throw new Error('error message')
}
foo()
Error 包含三个属性:
- messsage:创建 Error 对象时传入的 message;
- name:Error 的名称,通常和类的名称一致;
- stack:整个 Error 的错误信息,包括函数的调用栈,当我们直接打印 Error 对象时,打印的就是 stack;
Error 有一些自己的子类:
- RangeError:下标值越界时使用的错误类型;
- SyntaxError:解析语法错误时使用的错误类型;
- TypeError:出现类型错误时,使用的错误类型;
异常的处理
我们会发现在之前的代码中,一个函数抛出了异常,调用它的时候程序会被强制终止:
- 这是因为如果我们在调用一个函数时,这个函数抛出了异常,但是我们并没有对这个异常进行处理,那么这个异常会继续传递到上一个函数调用中;
- 而如果到了最顶层(全局)的代码中依然没有对这个异常的处理代码,这个时候就会报错并且终止程序的运行;
我们先来看一下这段代码的异常传递过程:
- foo 函数在被执行时会抛出异常,也就是我们的 bar 函数会拿到这个异常;
- 但是 bar 函数并没有对这个异常进行处理,那么这个异常就会被继续传递到调用 bar 函数的函数,也就是 test 函数;
- 但是 test 函数依然没有处理,就会继续传递到我们的全局代码逻辑中;
- 依然没有被处理,这个时候程序会终止执行,后续代码都不会再执行了;
function foo() {
throw new Error('error')
}
function bar() {
foo()
}
function test() {
bar()
}
test()
console.log('test后续代码')
异常的捕获
但是很多情况下当出现异常时,我们并不希望程序直接推出,而是希望可以正确的处理异常:
- 这个时候我们就可以使用 try catch
function foo() {
throw new Error('error')
}
function bar() {
try {
foo()
} catch (err) {
console.log(err.message)
} finally {
console.log('finally')
}
}
function test() {
bar()
}
test()
console.log('test后续代码')
在 ES10(ES2019)中,catch 后面绑定的 error 可以省略。
当然,如果有一些必须要执行的代码,我们可以使用 finally 来执行:
- finally 表示最终一定会被执行的代码结构;
- 注意:如果 try 和 finally 中都有返回值,那么会使用 finally 当中的返回值;