「JavaScript基础」深入学习async/await
nanshan 2024-10-05 18:33 9 浏览 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
- 用于编写异步程序
- 代码书写方式和同步编码十分相似,因此代码十分简洁易读
- 基于Promise
- 您可以使用try和catch常规的方法捕获异常
- ES8中引入了async/await,目前几乎所有浏览器都已支持这个特性(除了IE和Opera不支持)
- 你可以轻松设置断点,调试更容易。
从async开始学起
让我们从async关键字开始吧,这个关键词可以放在函数之前,如下所示:
async function f() { return 1; }
在函数之间加上async意味着:函数将返回一个Promise,虽然你的代码里没有显示的生命返回一个Promise,但是编译器会自动将其转换成一个Promise中,不信你可以使用Promise的then语法试试:
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 让我们以同步的方式更容易的编写异步代码,大大降低了编写异步函数的难度。
更多精彩内容,请微信关注”前端达人”公众号!
相关推荐
- F5负载均衡器如何通过irules实现应用的灵活转发?
-
F5是非常强大的商业负载均衡器。除了处理性能强劲,以及高稳定性之外,F5还可以通过irules编写强大灵活的转发规则,实现web业务的灵活应用。irules是基于TCL语法的,每个iRules必须包含...
- 映射域名到NAS
-
前面介绍已经将域名映射到家庭路由器上,现在只需要在路由器上设置一下端口转发即可。假设NAS在内网的IP是192.168.1.100,NAS管理端口2000.你的域名是www.xxx.com,配置外部端...
- 转发(Forward)和重定向(Redirect)的区别
-
转发是服务器行为,重定向是客户端行为。转发(Forward)通过RequestDispatcher对象的forward(HttpServletRequestrequest,HttpServletRe...
- SpringBoot应用中使用拦截器实现路由转发
-
1、背景项目中有一个SpringBoot开发的微服务,经过业务多年的演进,代码已经累积到令人恐怖的规模,亟需重构,将之拆解成多个微服务。该微服务的接口庞大,调用关系非常复杂,且实施重构的人员大部分不是...
- 公司想搭建个网站,网站如何进行域名解析?
-
域名解析是将域名指向网站空间IP,让人们通过注册的域名可以方便地访问到网站的一种服务。IP地址是网络上标识站点的数字地址,为方便记忆,采用域名来代替IP地址标识站点地址。域名解析就是域名到IP地址的转...
- 域名和IP地址什么关系?如何通过域名解析IP?
-
一般情况下,访客通过域名和IP地址都能访问到网站,那么两者之间有什么关系吗?本文中科三方针对域名和IP地址的关系和区别,以及如何实现域名与IP的绑定做下介绍。域名与IP地址之间的关系IP地址是计算机的...
- 分享网站域名301重定向的知识
-
网站域名做301重定向操作时,一般需要由专业的技术来协助完成,如果用户自己在维护,可以按照相应的说明进行操作。好了,下面说说重点,域名301重定向的操作步骤。首先,根据HTTP协议,在客户端向服务器发...
- NAS外网到底安全吗?一文看懂HTTP/HTTPS和SSL证书
-
本内容来源于@什么值得买APP,观点仅代表作者本人|作者:可爱的小cherry搭好了NAS,但是不懂做好网络加密,那么隐私泄露也会随时发生!大家好,这里是Cherry,喜爱折腾、玩数码,热衷于分享数...
- ForwardEmail免费、开源、加密的邮件转发服务
-
ForwardEmail是一款免费、加密和开源的邮件转发服务,设置简单只需4步即可正常使用,通过测试来看也要比ImprovMX好得多,转发近乎秒到且未进入垃圾箱(仅以Mailbox.org发送、Out...
- 使用CloudFlare进行域名重定向
-
当网站变更域名的时候,经常会使用域名重定向的方式,将老域名指向到新域名,这通常叫做:URL转发(URLFORWARDING),善于使用URL转发,对SEO来说非常有用,因为用这种方式能明确告知搜索引...
- 要将端口5002和5003通过Nginx代理到一个域名上的操作笔记
-
要将端口5002和5003通过Nginx代理到域名www.4rvi.cn的不同路径下,请按照以下步骤配置Nginx:步骤说明创建或编辑Nginx配置文件通常配置文件位于/etc/nginx/sites...
- SEO浅谈:网站域名重定向的三种方式
-
在大多数情况下,我们输入网站访问网站的时候,很难发现www.***.com和***.com的区别,因为一般的网站主,都会把这两个域名指向到同一网站。但是对于网站运营和优化来说,www.***.com和...
- 花生壳出现诊断域名与转发服务器ip不一致的解决办法
-
出现诊断域名与转发服务器ip不一致您可以:1、更改客户端所处主机的drs为223.5.5.5备用dns为119.29.29.29;2、在windows上进入命令提示符输入ipconfig/flush...
- 涨知识了!带你认识什么是域名
-
1、什么是域名从技术角度来看,域名是在Internet上解决IP地址对应的一种方法。一个完整的域名由两个或两个以上部分组成,各部分之间用英文的句号“.”来分隔。如“abc.com”。其中“com”称...
- 域名被跳转到其他网站是怎么回事
-
当你输入域名时被跳转到另一个网站,这可能是由几种原因造成的:一、域名可能配置了域名转发服务。无论何时有人访问域名,比如.com、.top等,都会自动重定向到另一个指定的URL,这通常是在域名注册商设...
你 发表评论:
欢迎- 一周热门
-
-
爱折腾的特斯拉车主必看!手把手教你TESLAMATE的备份和恢复
-
如何在安装前及安装后修改黑群晖的Mac地址和Sn系列号
-
[常用工具] OpenCV_contrib库在windows下编译使用指南
-
WindowsServer2022|配置NTP服务器的命令
-
Ubuntu系统Daphne + Nginx + supervisor部署Django项目
-
WIN11 安装配置 linux 子系统 Ubuntu 图形界面 桌面系统
-
解决Linux终端中“-bash: nano: command not found”问题
-
NBA 2K25虚拟内存不足/爆内存/内存占用100% 一文速解
-
Linux 中的文件描述符是什么?(linux 打开文件表 文件描述符)
-
K3s禁用Service Load Balancer,解决获取浏览器IP不正确问题
-
- 最近发表
- 标签列表
-
- linux 查询端口号 (58)
- docker映射容器目录到宿主机 (66)
- 杀端口 (60)
- yum更换阿里源 (62)
- internet explorer 增强的安全配置已启用 (65)
- linux自动挂载 (56)
- 禁用selinux (55)
- sysv-rc-conf (69)
- ubuntu防火墙状态查看 (64)
- windows server 2022激活密钥 (56)
- 无法与服务器建立安全连接是什么意思 (74)
- 443/80端口被占用怎么解决 (56)
- ping无法访问目标主机怎么解决 (58)
- fdatasync (59)
- 405 not allowed (56)
- 免备案虚拟主机zxhost (55)
- linux根据pid查看进程 (60)
- dhcp工具 (62)
- mysql 1045 (57)
- 宝塔远程工具 (56)
- ssh服务器拒绝了密码 请再试一次 (56)
- ubuntu卸载docker (56)
- linux查看nginx状态 (63)
- tomcat 乱码 (76)
- 2008r2激活序列号 (65)