百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

「JavaScript基础」深入学习async/await

nanshan 2024-10-05 18:33 15 浏览 0 评论

大家好,前几篇文章我们一起学习了「JavaScript基础」Promise使用指南, 明白了ES6增加的新特性——Promise让我们能够更加优雅的书写回调函数,清楚了Promise有哪些状态,以及如何编写Promise的相关代码。本篇文章,小编将和大家一起学习异步编程的未来——async/await,它会打破你对上篇文章Promise的认知,竟然异步代码还能这么写! 但是别太得意,你需要深入理解Promise后,才能更好的的驾驭async/await,因为async/await是基于Promise的,没有理解Promise,小编强烈建议各位再看看「JavaScript基础」Promise使用指南。如果你一旦掌握了如何使用async/await,就没必要使用Promise了(除非你需要将回调类型的API转换为async/await,你需要使用到Promise)。

关于async / await

  1. 用于编写异步程序
  2. 代码书写方式和同步编码十分相似,因此代码十分简洁易读
  3. 基于Promise
  4. 您可以使用try和catch常规的方法捕获异常
  5. ES8中引入了async/await,目前几乎所有浏览器都已支持这个特性(除了IE和Opera不支持)
  6. 你可以轻松设置断点,调试更容易。

从async开始学起

让我们从async关键字开始吧,这个关键词可以放在函数之前,如下所示:

async function f() { 
 return 1; 
}

在函数之间加上async意味着:函数将返回一个Promise,虽然你的代码里没有显示的生命返回一个Promise,但是编译器会自动将其转换成一个Promise中,不信你可以使用Promisethen语法试试:

async function f() { 
 return 1; 
} 
f().then(alert); // 1

…如果你不放心的话,你可以再代码里明确返回一个Promise,输出结果是相同的。

async function f() { 
 return Promise.resolve(1); 
} 
f().then(alert); // 1

很简单吧,小编之所以说 async/await 是基于Promise是没毛病的,async确保函数返回一个Promise,很简单吧,不仅如此,还有一个关键字await,await只能在async中运行。

等待——await

await的基本语法:

let value=await promise;

该关键字的await的意思就是让JS编译器等待Promise并返回结果。接下来我们看一段简单的示例:

async function f() { 
 let promise = new Promise((resolve, reject) => { 
 setTimeout(() => resolve("done!"), 1000) 
 }); 
 let result = await promise; // wait till the promise resolves (*) 
 alert(result); // "done!" 
} 
f();

函数执行将会在 let result = await promise 这一行暂停,直到Promise返回结果,因此上述代码将会1秒后,在浏览器弹出“done”的提示框。

小编在此强调下:

  • await的字面意思就是让JavaScript等到Promise结束,然后输出结果。这里并不会占用CPU资源,因为引擎可以同时执行其他任务:其他脚本或处理事件。
  • 不能单独使用await,必须在async函数作用域下使用,否则将会报出异常“Error: await is only valid in async function”,比如以下代码:
function f() { 
 let promise = Promise.resolve(1); 
 let result = await promise; // Syntax error 
}

接下来,小编将和大家一起来亲自动手实践以下内容:

  • async与Promise.then的结合使用,依次处理多个执行结果
  • 使用await替代Promise.then,依次处理多个执行结果
  • 同时等待多个执行结果
  • 使用Promise.all收集多个结果
  • 使用try-catch捕获异常
  • 如何处理Promise.all中抛出的错误
  • 使用finally确保函数执行

一起动手之前,确保你安装了Node,NPM相关工具,谷歌浏览器,为了预览代码效果,小编使用 npm install http-server -g 命令快速部署了web服务环境,方便我们运行代码。接下来,我们写一个火箭发射场景的小例子。

async与Promise.then的结合使用,依次处理多个执行结果

  • 通过控制台命令切换至工作区
  • 创建一个async-function-Promise-chain的文件夹
  • 在main.js中用创建第一个返回随机函数的async函数getRandomNumber:
async function getRandomNumber() { 
 console.log('Getting random number.'); 
 return Math.random(); 
}
  • 在创建一个async函数determinReadyToLaunch:如果传入参数大于0.5将返回True
async function deteremineReadyToLaunch(percentage) { 
 console.log('Determining Ready to launch.'); 
 return percentage>0.5; 
}
  • 创建第三个async函数reportResults,如果传入参数为True将进入倒计时发射
async function reportResults(isReadyToLaunch) { 
 if (isReadyToLaunch) { 
 console.log('Rocket ready to launch. Initiate countdown: '); 
 } else { 
 console.error('Rocket not ready. Abort mission: '); 
 } 
}
  • 创建一个main函数,调用getRandomNumber函数,并且通过Promise.then方法相机调用determineReadyToLaunch和reportResults函数
export function main() { 
 console.log('Before Promise created'); 
 getRandomNumber() 
 .then(deteremineReadyToLaunch) 
 .then(reportResults) 
 console.log('After Promise created'); 
}
  • 新建一个html文件引入main.js
<html> 
<script type="module"> 
 import {main} from './main10.js'; 
 main(); 
</script> 
<body> 
</body> 
</html>
  • 在工作区域运行 http-server 命令,你将会看到如下输出

使用await替代Promise.then,依次处理多个执行结果

上一节,我们使用Promise.then依次处理了多个执行结果,本小节,小编将使用await实现同样的功能,具体操作如下:

  • 通过控制台命令切换至工作区
  • 创建一个async-function-Promise-chain的文件夹
  • 在main.js中用创建第一个返回随机函数的async函数getRandomNumber:
async function getRandomNumber() { 
 console.log('Getting random number.'); 
 return Math.random(); 
}
  • 在创建一个async函数determinReadyToLaunch:如果传入参数大于0.5将返回True
async function deteremineReadyToLaunch(percentage) { 
 console.log('Determining Ready to launch.'); 
 return percentage>0.5; 
}
  • 创建第三个async函数reportResults,如果传入参数为True将进入倒计时发射
async function reportResults(isReadyToLaunch) { 
 if (isReadyToLaunch) { 
 console.log('Rocket ready to launch. Initiate countdown: '); 
 } else { 
 console.error('Rocket not ready. Abort mission: '); 
 } 
}
  • 创建一个main函数,调用getRandomNumber函数,并且通过Promise.then方法相机调用determineReadyToLaunch和reportResults函数
export async function main() { 
 const randomNumber = await getRandomNumber(); 
 const ready = await deteremineReadyToLaunch(randomNumber); 
 await reportResults(ready); 
}
  • 在工作区域运行 http-server 命令,你将会看到如下输出

同时等待多个执行结果

有时候我们需要同时启动多个异步,无需依次等待结果消耗时间,接下来的例子可以使用await 同时启动和等待多个结果。

  • 通过控制台命令切换至工作区
  • 创建一个await-concurrently的文件夹
  • 创建三个函数功能checkEngines,checkFlightPlan,和checkNavigationSystem用来记录信息时,这三个函数都返回一个Promise,示例代码如下:
function checkEngines() { 
 console.log('checking engine'); 
 return new Promise(function (resolve) { 
 setTimeout(function() { 
 console.log('engine check completed'); 
 resolve(Math.random() < 0.9) 
 }, 250) 
 }); 
} 
function checkFlightPlan() { 
 console.log('checking flight plan'); 
 return new Promise(function (resolve) { 
 setTimeout(function() { 
 console.log('flight plan check completed'); 
 resolve(Math.random() < 0.9) 
 }, 350) 
 }); 
} 
function checkNavigationSystem() { 
 console.log('checking navigation system'); 
 return new Promise(function (resolve) { 
 setTimeout(function() { 
 console.log('navigation system check completed'); 
 resolve(Math.random() < 0.9) 
 }, 450) 
 }); 
}
  • 创建一个async 的main函数调用上一步创建函数。将每个返回的值分配给局部变量。然后等待Promise的结果,并输出结果:
 export async function main() { 
 const enginePromise = checkEngines(); 
 const flighPlanPromise = checkFlightPlan(); 
 const navSystemPromise = checkNavigationSystem(); 
 const enginesOk = await enginePromise; 
 const flighPlanOk = await flighPlanPromise; 
 const navigationOk = await navSystemPromise; 
 if (enginesOk && flighPlanOk && navigationOk) { 
 console.log('All systems go, ready to launch: '); 
 } else { 
 console.error('Abort the launch: '); 
 if (!enginesOk) { 
 console.error('engines not ready'); 
 } 
 if (flighPlanOk) { 
 console.error('error found in flight plan'); 
 } 
 if (navigationOk) { 
 console.error('error found in navigation systems'); 
 } 
 } 
}
  • 在工作区域运行 http-server 命令,你将会看到如下输出

使用Promise.all收集多个结果

在上一小节中,我们一起学习了如何触发多个异步并等待多个异步结果。上一节我们只使用了asyc/ await,本节小编和大家一起使用Promise.all来收集多个异步的结果,在某些情况下,尽量使用Promise相关的API,具体的代码如下:

  • 通过控制台命令切换至工作区
  • 创建一个Promise-all-collect-concurrently的文件夹

创建三个函数功能checkEngines,checkFlightPlan,和checkNavigationSystem用来记录信息时,这三个函数都返回一个Promise,示例代码如下:

function checkEngines() { 
 console.log('checking engine'); 
 return new Promise(function (resolve) { 
 setTimeout(function() { 
 console.log('engine check completed'); 
 resolve(Math.random() < 0.9) 
 }, 250) 
 }); 
} 
function checkFlightPlan() { 
 console.log('checking flight plan'); 
 return new Promise(function (resolve) { 
 setTimeout(function() { 
 console.log('flight plan check completed'); 
 resolve(Math.random() < 0.9) 
 }, 350) 
 }); 
} 
function checkNavigationSystem() { 
 console.log('checking navigation system'); 
 return new Promise(function (resolve) { 
 setTimeout(function() { 
 console.log('navigation system check completed'); 
 resolve(Math.random() < 0.9) 
 }, 450) 
 }); 
}
  • 创建一个async 的main函数调用上一步创建函数。使用Promise.all收集多个结果,将结果返回给变量,代码实现如下:
export async function main() { 
 const prelaunchChecks = [ 
 checkEngines(), 
 checkFlightPlan(), 
 checkNavigationSystem() 
 ]; 
 const checkResults = await Promise.all(prelaunchChecks); 
 const readyToLaunch = checkResults.reduce((acc, curr) => acc && 
 curr); 
 if (readyToLaunch) { 
 console.log('All systems go, ready to launch: '); 
 } else { 
 console.error('Something went wrong, abort the launch: '); 
 } } 
}
  • 在工作区域运行 http-server 命令,你将会看到如下输出

Promise.all接收多个promise的数组,并整体返回一个Promise,如果和上一小节的代码进行比较,代码量少了不少,但是也有个问题,不能返回是哪一步失败。

使用try-catch捕获异常

并非所有的async都能成功返回,我们需要能够处理程序的异常,在本小节中,你将会看到如何使用try-catch捕获async函数引发的错误,具体操作的流程如下:

  • 通过控制台命令切换至工作区
  • 创建一个async-errors-try-catch的文件夹
  • 创建一个抛出错误的async函数addBoosters
async function addBoosters() { 
 throw new Error('Unable to add Boosters'); 
}
  • 创建一个async函数,performGuidanceDiagnostic它也会抛出一个错误:
async function performGuidanceDiagnostic (rocket) { 
 throw new Error('Unable to finish guidance diagnostic')); 
}
  • 创建一个async的main函数调用函数addBosters与performGuidanceDiagnostic ,使用try-catch处理错误:
 export async function main() { 
 console.log('Before Check'); 
 try { 
 await addBosters(); 
 await performGuidanceDiagnostic(); 
 } catch (e) { 
 console.error(e); 
 } 
} 
console.log('After Check');
  • 在工作区域运行 http-server 命令,你将会看到如下输出

从输出看出,我们使用我们熟悉的try-catch捕获到了异常,如果第一个发生异常,第二个就不会执行,同时将会记录到发生的异常,并输出到控制台,在下一小节,我们一起将学习到如何使用try-catch捕获同时运行多个异步操作的异常。

如何处理Promise.all中抛出的错误

在上面的小节中,我们使用了Promise.all来收集多个异步的执行结果。在收集错误状态,Promise.all更有趣。通常,我们在处理多个错误时,同时显示多个错误信息,我们必须编写相关的业务逻辑。但是,在这小节,你将会使用Promise.all和try-catch捕获异常,无需编写复杂的布尔逻辑处理业务,具体如何实现示例如下:

  • 通过控制台命令切换至工作区
  • 创建一个Promise-all-collect-concurrently的文件夹
  • 创建三个async功能checkEngines,checkFlightPlan以及checkNavigationSystem函数用来记录信息时,返回Promise,一个成功的值的信息和一个失败值的信息:
function checkEngines() { 
 console.log('checking engine'); 
 return new Promise(function (resolve) { 
 setTimeout(function() { 
 console.log('engine check completed'); 
 resolve(Math.random() < 0.9) 
 }, 250) 
 }); 
} 
function checkFlightPlan() { 
 console.log('checking flight plan'); 
 return new Promise(function (resolve) { 
 setTimeout(function() { 
 console.log('flight plan check completed'); 
 resolve(Math.random() < 0.9) 
 }, 350) 
 }); 
} 
function checkNavigationSystem() { 
 console.log('checking navigation system'); 
 return new Promise(function (resolve) { 
 setTimeout(function() { 
 console.log('navigation system check completed'); 
 resolve(Math.random() < 0.9) 
 }, 450) 
 }); 
}

创建一个async的main函数调用每个在上一步中创建的功能函数。等待结果,捕获并记录引发的任何错误。如果没有抛出错误,则记录成功:

 export async function main() { 
 try { 
 const prelaunchChecks = [ 
 checkEngines, 
 checkFlightPlan, 
 checkNavigationSystem 
 ]; 
 await Promise.all(prelauchCheck.map((check) => check()); 
 console.log('All systems go, ready to launch: '); 
 } catch (e) { 
 console.error('Aborting launch: '); 
 console.error(e); 
 } 
 } 
}
  • 在工作区域运行 http-server 命令,你将会看到如下输出

Promise.all返回一个Promise,当await在错误状态下,会抛出异常。三个异步promise同时执行,如果其中一个或多个错误得到满足,则会抛出一个或多个错误;

你会发现只有一个错误会被记录下来,与同步代码一样,我们的代码可能会抛出多个异常,但只有一会被catch块捕获并记录。

使用finally确保函数执行

错误处理可能会变得相当复杂。有些情况,其中您希望错误继续冒泡调用堆栈以便执行其它更高级别处理。在这些情况下,您可能还需要执行一些清理任务。本小节,你将了解如何使用finally以确保执行某些代码,而不管错误状态如何,具体如何实现示例如下:

  • 通过控制台命令切换至工作区
  • 创建一个Promise-all-collect-concurrently的文件夹
  • 创建三个async功能checkEngines,checkFlightPlan以及checkNavigationSystem函数用来记录信息时,返回Promise,一个成功的值的信息和一个失败值的信息:
function checkEngines() { 
 console.log('checking engine'); 
 return new Promise(function (resolve, reject) { 
 setTimeout(function () { 
 if (Math.random() > 0.5) { 
 reject(new Error('Engine check failed')); 
 } else { 
 console.log('Engine check completed'); 
 resolve(); 
 } 
 }, 250) 
 }); 
} 
function checkFlightPlan() { 
 console.log('checking flight plan'); 
 return new Promise(function (resolve, reject) { 
 setTimeout(function () { 
 if (Math.random() > 0.5) { 
 reject(new Error('Flight plan check failed')); 
 } else { 
 console.log('Flight plan check completed'); 
 resolve(); 
 } 
 }, 350) 
 }); 
} 
function checkNavigationSystem() { 
 console.log('checking navigation system'); 
 return new Promise(function (resolve, reject) { 
 setTimeout(function () { 
 if (Math.random() > 0.5) { 
 reject(new Error('Navigation system check failed')); 
 } else { 
 console.log('Navigation system check completed'); 
 resolve(); 
 } 
 }, 450) 
 }); 
}
  • 创建一个asyncperformCheck函数,调用上一步中创建的每个函数。等待结果,并用于finally记录完整的消息:
async function performChecks() { 
 console.log('Starting Pre-Launch Checks'); 
 try { 
 const prelaunchChecks = [ 
 checkEngines, 
 checkFlightPlan, 
 checkNavigationSystem 
 ]; 
 return Promise.all(prelauchCheck.map((check) => check()); 
 } finally { 
 console.log('Completed Pre-Launch Checks'); 
 } 
 }
  • 创建一个async的main函数调该函数performChecks。等待结果,捕获并记录引发的错误。
export async function main() { 
 try { 
 await performChecks(); 
 console.log('All systems go, ready to launch: '); 
 } catch (e) { 
 console.error('Aborting launch: '); 
 console.error(e); 
 } 
}
  • 在工作区域运行 http-server 命令,你将会看到如下输出

与上一小节一样,错误在main函数中进行捕获,由于finally的存在,让我清楚的知道performChecks确保执行输出已完成。你可以设想,处理错误是一个重要的任务,并且async/await允许我们使用try/catch的相同方式处理异步和同步代码的错误,大大简化了我们处理错误的工作量,让代码更加简洁。

用async/await改写上篇文章Promise的例子

上篇文章「JavaScript基础」Promise使用指南的最后,我们使用Promise的方法改写了基于回调的例子,本文的最后,我们将用今天学到的内容 async/await改写这个例子, 如何实现呢,代码如下:

const fs = require('fs'); 
const path = require('path'); 
const postsUrl = path.join(__dirname, 'db/posts.json'); 
const commentsUrl = path.join(__dirname, 'db/comments.json'); 
//return the data from our file 
function loadCollection(url) { 
 return new Promise(function(resolve, reject) { 
 fs.readFile(url, 'utf8', function(error, data) { 
 if (error) { 
 reject('error'); 
 } else { 
 resolve(JSON.parse(data)); 
 } 
 }); 
 }); 
} 
//return an object by id 
function getRecord(collection, id) { 
 return new Promise(function(resolve, reject) { 
 const data = collection.find(function(element){ 
 return element.id == id; 
 }); 
 resolve(data); 
 }); 
} 
//return an array of comments for a post 
function getCommentsByPost(comments, postId) { 
 return comments.filter(function(comment){ 
 return comment.postId == postId; 
 }); 
} 
async function getPost(){ 
 try { 
 const posts = await loadCollection(postsUrl); 
 const post = await getRecord(posts, "001"); 
 const comments = await loadCollection(commentsUrl); 
 const postComments = await getCommentsByPost(comments, post.id); 
 console.log(post); 
 console.log(postComments); 
 } catch (error) { 
 console.log(error); 
 } 
} 
getPost();

和Promise的方式相比,async/await 的实现方式是不是更直观更容易理解呢,让我几乎能用同步的方式编写异步代码。

结束语

本节内容就介绍到这里,我们学会了如何使用 async/await 的使用,并且学会了如何与Promise相关API进行结合,async/await 让我们以同步的方式更容易的编写异步代码,大大降低了编写异步函数的难度。

更多精彩内容,请微信关注”前端达人”公众号!

相关推荐

0722-6.2.0-如何在RedHat7.2使用rpm安装CDH(无CM)

文档编写目的在前面的文档中,介绍了在有CM和无CM两种情况下使用rpm方式安装CDH5.10.0,本文档将介绍如何在无CM的情况下使用rpm方式安装CDH6.2.0,与之前安装C5进行对比。环境介绍:...

ARM64 平台基于 openEuler + iSula 环境部署 Kubernetes

为什么要在arm64平台上部署Kubernetes,而且还是鲲鹏920的架构。说来话长。。。此处省略5000字。介绍下系统信息;o架构:鲲鹏920(Kunpeng920)oOS:ope...

生产环境starrocks 3.1存算一体集群部署

集群规划FE:节点主要负责元数据管理、客户端连接管理、查询计划和查询调度。>3节点。BE:节点负责数据存储和SQL执行。>3节点。CN:无存储功能能的BE。环境准备CPU检查JDK...

在CentOS上添加swap虚拟内存并设置优先级

现如今很多云服务器都会自己配置好虚拟内存,当然也有很多没有配置虚拟内存的,虚拟内存可以让我们的低配服务器使用更多的内存,可以减少很多硬件成本,比如我们运行很多服务的时候,内存常常会满,当配置了虚拟内存...

国产深度(deepin)操作系统优化指南

1.升级内核随着deepin版本的更新,会自动升级系统内核,但是我们依旧可以通过命令行手动升级内核,以获取更好的性能和更多的硬件支持。具体操作:-添加PPAs使用以下命令添加PPAs:```...

postgresql-15.4 多节点主从(读写分离)

1、下载软件[root@TX-CN-PostgreSQL01-252software]#wgethttps://ftp.postgresql.org/pub/source/v15.4/postg...

Docker 容器 Java 服务内存与 GC 优化实施方案

一、设置Docker容器内存限制(生产环境建议)1.查看宿主机可用内存bashfree-h#示例输出(假设宿主机剩余16GB可用内存)#Mem:64G...

虚拟内存设置、解决linux内存不够问题

虚拟内存设置(解决linux内存不够情况)背景介绍  Memory指机器物理内存,读写速度低于CPU一个量级,但是高于磁盘不止一个量级。所以,程序和数据如果在内存的话,会有非常快的读写速度。但是,内存...

Elasticsearch性能调优(5):服务器配置选择

在选择elasticsearch服务器时,要尽可能地选择与当前业务量相匹配的服务器。如果服务器配置太低,则意味着需要更多的节点来满足需求,一个集群的节点太多时会增加集群管理的成本。如果服务器配置太高,...

Es如何落地

一、配置准备节点类型CPU内存硬盘网络机器数操作系统data节点16C64G2000G本地SSD所有es同一可用区3(ecs)Centos7master节点2C8G200G云SSD所有es同一可用区...

针对Linux内存管理知识学习总结

现在的服务器大部分都是运行在Linux上面的,所以,作为一个程序员有必要简单地了解一下系统是如何运行的。对于内存部分需要知道:地址映射内存管理的方式缺页异常先来看一些基本的知识,在进程看来,内存分为内...

MySQL进阶之性能优化

概述MySQL的性能优化,包括了服务器硬件优化、操作系统的优化、MySQL数据库配置优化、数据库表设计的优化、SQL语句优化等5个方面的优化。在进行优化之前,需要先掌握性能分析的思路和方法,找出问题,...

Linux Cgroups(Control Groups)原理

LinuxCgroups(ControlGroups)是内核提供的资源分配、限制和监控机制,通过层级化进程分组实现资源的精细化控制。以下从核心原理、操作示例和版本演进三方面详细分析:一、核心原理与...

linux 常用性能优化参数及理解

1.优化内核相关参数配置文件/etc/sysctl.conf配置方法直接将参数添加进文件每条一行.sysctl-a可以查看默认配置sysctl-p执行并检测是否有错误例如设置错了参数:[roo...

如何在 Linux 中使用 Sysctl 命令?

sysctl是一个用于配置和查询Linux内核参数的命令行工具。它通过与/proc/sys虚拟文件系统交互,允许用户在运行时动态修改内核参数。这些参数控制着系统的各种行为,包括网络设置、文件...

取消回复欢迎 发表评论: