这是我参与8月更文挑战的第7天,活动详情查看: 8月更文挑战
如果您正在阅读此博客,您可能对JavaScript中的异步编程有些熟悉,并且您可能想知道它在TypeScript中是如何工作的。
由于TypeScript是JavaScript的超集,异步/wait的工作原理相同,但有一些额外的好东西和类型安全性。TypeScript使您能够对预期结果进行类型保护,甚至类型检查错误,这有助于您在开发过程中的早期检测到错误。
async/await 本质
上
是 promises的
语法糖
,也就是说关键字是 promises 的包装器。一个函数总是返回一个promise。即使您省略了关键字,编译器也会将您的函数包装在一个立即解析的 promise 中。
async/await``async``Promise
请允许我演示:
const myAsynFunction = async (url: string): Promise<T> => {
const { data } = await fetch(url)
return data
const immediatelyResolvedPromise = (url: string) => {
const resultPromise = new Promise((resolve, reject) => {
resolve(fetch(url))
return resultPromise
虽然它们看起来完全不同,但上面的代码片段或多或少是等价的。异步/等待只是使您能够以更同步的方式编写代码,并为您在线取消承诺。当您处理复杂的异步模式时,这是强大的。
为了充分利用语法,您需要对 promise 有基本的了解。让我们在基本层面上仔细看看 Promises。async/await
TypeScript中的承诺是什么?
根据词典的说法,在英语中,承诺是“一个人将做某件事或某件事会发生的声明或保证”。在JavaScript中,promise是指期望在特定时间发生一些事情,并且您的应用程序依赖于该未来事件的结果来执行某些其他任务。
为了表明我的意思,我将分解一个现实世界的示例,并将其转换为伪代码,然后转换为实际的TypeScript代码。
假设我有草坪要修剪。我联系了一家割草公司,该公司承诺在几个小时内割草。反过来,我承诺,只要修剪得当,之后会立即支付他们。
你能发现图案吗?需要注意的第一件事是,第二个事件完全依赖于前一个事件。如果第一个事件的承诺得到兑现,下一个事件将执行。然后,在这种情况下的承诺要么兑现,要么被拒绝,要么仍然悬而未决。
在我们编写完整的代码之前,检查promise的语法是有意义的——具体地说,一个解析为字符串的承诺示例。
We declared a promise
with the new + Promise
keyword, which takes in the resolve
and reject
arguments. Now let’s write a promise for the flow chart above.
// 我向公司发送请求。这是同步的
// 公司以承诺回复
const angelMowersPromise = new Promise<string>((resolve, reject) => {
// a resolved promise after certain hours
setTimeout(() => {
resolve('We finished mowing the lawn')
}, 100000) // resolves after 100,000ms
reject("We couldn't mow the lawn")
const myPaymentPromise = new Promise<Record<string, number | string>>((resolve, reject) => {
// a resolved promise with an object of 1000 Euro payment
// and a thank you message
setTimeout(() => {
resolve({
amount: 1000,
note: 'Thank You',
}, 100000)
// reject with 0 Euro and an unstatisfatory note
reject({
amount: 0,
note: 'Sorry Lawn was not properly Mowed',
在上面的代码中,我们声明了公司的承诺和我们的承诺。公司承诺要么在 100,000 毫秒后解决,要么被拒绝。一个Promise
总是处于三种状态之一:resolved
如果没有错误,rejected
如果遇到一个错误,或者pending
如果promise
已经既不拒绝,也不满足。在我们的例子中,它属于这个100000ms
时期。
但是我们如何以顺序和同步的方式执行任务呢?这就是then
关键字的用武之地。没有它,函数只会按照它们解析的顺序运行。
顺序执行.then
现在我们可以链接 promises,这允许它们与. 这就像正常的人类语言一样——做这个,然后那个,然后那个,等等。.then
angelMowersPromise
.then(() => myPaymentPromise.then(res => console.log(res)))
.catch(error => console.log(error))
上面的代码将运行angelMowersPromise
. 如果没有错误,它将运行myPaymentPromise
. 如果两个 promise 中的任何一个出现错误,都会在catch
块中捕获。
下面我们来看一个比较技术性的例子。前端编程中常见的任务是提出网络请求并相应地响应结果。
以下是从远程服务器获取员工列表的请求。
const api = 'http://dummy.restapiexample.com/api/v1/employees'
fetch(api)
.then(response => response.json())
.then(employees => employees.forEach(employee => console.log(employee.id)) // logs all employee id
.catch(error => console.log(error.message))) // logs any error from the promise
有时您可能需要并行或顺序执行多个 promise。诸如或 之类的构造在这些场景中特别有用。Promise.all``Promise.race
例如,假设您需要获取1000个GitHub用户的列表,然后使用ID提出额外的请求,为每个用户获取头像。您不一定想等待序列中的每个用户;您只需要所有获取的头像。稍后,当我们讨论Promise.all
时,我们会更详细地研究这个问题。
现在您已经基本掌握了 Promise,让我们看一下语法。async/await
async/await
异步/等待是一种令人惊讶的简单语法,可用于处理promise。它提供了一个易于读写promise的界面,使其看起来同步。
An将始终返回 a 。即使省略关键字,编译器也会将函数包装在一个立即解析的. 这使您可以将函数的返回值视为 a ,这在您需要解析大量异步函数时非常有用。async/await``Promise``Promise``Promise
async
Promise
顾名思义,async
总是与await
. 也就是说,你只能await
在一个async
函数内部。该async
函数通知编译器这是一个异步函数。
如果我们从上面转换承诺,语法如下:
const myAsync = async (): Promise<Record<string, number | string>> => {
await angelMowersPromise
const response = await myPaymentPromise
return response
正如您立即看到的那样,这看起来更具可读性并且看起来是同步的。我们在第 3 行告诉编译器在angelMowersPromise
做任何其他事情之前等待执行。然后,我们从myPaymentPromise
.
您可能已经注意到我们省略了错误处理。我们可以在承诺catch
之后的块上做到这一点。但是如果我们遇到错误会发生什么?这导致我们到..then``try/catch
使用错误处理try/catch
我们将参考员工获取示例,以操作中的错误处理,因为它可能会在网络请求上遇到错误。
例如,假设服务器已关闭,或者我们发送了一个格式错误的请求。我们需要暂停执行,以防止我们的程序崩溃。语法会是这样的:
interface Employee {
id: number
employee_name: string
employee_salary: number
employee_age: number
profile_image: string
const fetchEmployees = async (): Promise<Array<Employee> | string> => {
const api = 'http://dummy.restapiexample.com/api/v1/employees'
try {
const response = await fetch(api)
const { data } = await response.json()
return data
} catch (error) {
if (error) {
return error.message
我们将函数作为async
函数启动。我们期望返回值是typeof
员工数组或一串错误消息。因此,Promise 的类型是.Promise<Array<Employee> | string>
里面try
块是我们期望的功能来运行,如果没有错误的表达。该catch
块捕获出现的任何错误。在这种情况下,我们只需返回对象的message
属性error
。
这样做的好处在于,首先在try
块中发生的任何错误都会被抛出并在catch
块中捕获。未捕获的异常可能导致难以调试的代码甚至破坏整个程序。
与并行执行Promise.all
正如我之前所说,有时我们需要承诺来并行执行。
让我们看看我们员工API的一个例子。假设我们首先需要获取所有员工,然后获取他们的名字,然后从名字中生成一封电子邮件。显然,我们需要以同步和并行的方式执行函数,这样一个就不会阻止另一个。
在这种情况下,我们将使用Promise.all
。根据Mozilla的说法,“Promise.all
通常在启动多个异步任务并发运行并为其结果创建promise后使用,以便等待所有任务完成。”
在伪代码中,我们会有这样的东西:
获取所有用户=>/employee
等待所有用户数据。从每个用户中提取id
。获取每个用户=>/employee/{id}
从每个用户的用户名为他们生成电子邮件
const baseApi = 'https://reqres.in/api/users?page=1'
const userApi = 'https://reqres.in/api/user'
const fetchAllEmployees = async (url: string): Promise<Employee[]> => {
const response = await fetch(url)
const { data } = await response.json()
return data
const fetchEmployee = async (url: string, id: number): Promise<Record<string, string>> => {
const response = await fetch(`${url}/${id}`)
const { data } = await response.json()
return data
const generateEmail = (name: string): string => {
return `${name.split(' ').join('.')}@company.com`
const runAsyncFunctions = async () => {
try {
const employees = await fetchAllEmployees(baseApi)
Promise.all(
employees.map(async user => {
const userName = await fetchEmployee(userApi, user.id)
const emails = generateEmail(userName.name)
return emails
} catch (error) {
console.log(error)
runAsyncFunctions()
在上面的代码中,fetchEmployees
从baseApi
. 我们await
响应,将其转换为JSON
,然后返回转换后的数据。
要记住的最重要的概念是我们如何async
使用await
关键字在函数内一行一行地顺序执行代码。如果我们尝试将数据转换为尚未完全等待的 JSON,则会出现错误。相同的概念适用于fetchEmployee
,只是我们只获取一个员工。更有趣的部分是runAsyncFunctions
,我们同时运行所有异步函数。
首先,将所有方法包装runAsyncFunctions
在一个块内。接下来,获取所有员工的结果。我们需要每个员工的 获取他们各自的数据,但我们最终需要的是有关员工的信息。try/catch``await``id
这是我们可以调用并发处理所有的地方。每个都对所有员工同时执行。然后使用来自员工信息的等待数据为每个具有该功能的员工生成电子邮件。Promise.all``Promises``fetchEmployee
Promise``generateEmail
在发生错误的情况下,它会像往常一样从失败的 promise 传播到,然后成为我们可以在块内捕获的异常。Promise.all``catch
async
并且await
使我们的方式,看起来和行为就像同步代码编写异步代码。这使得代码更易于阅读、编写和推理。
当您在 TypeScript 中处理下一个异步项目时,我将介绍一些需要牢记的关键概念。
await
只在async
函数内部有效
用async
关键字标记的函数总是返回一个Promise
如果里面的返回值async
没有返回 a Promise
,它将被包裹在一个立即解析的Promise
await
遇到关键字时暂停执行,直到 aPromise
完成
await
要么返回一个满足的结果,Promise
要么抛出一个被拒绝的异常Promise