JavaScript基础——深入学习async/await

7次阅读

共计 11684 个字符,预计需要花费 30 分钟才能阅读完成。

本文由云 + 社区发表
本篇文章,小编将和大家一起学习异步编程的未来——async/await,它会打破你对上篇文章 Promise 的认知,竟然异步代码还能这么写! 但是别太得意,你需要深入理解 Promise 后,才能更好的的驾驭 async/await,因为 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 ‘./main.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 的异常。
如何捕获 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, 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)
});
}
创建一个 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 的实现方式是不是更直观更容易理解呢,让我几乎能用同步的方式编写异步代码。
此文已由作者授权腾讯云 + 社区发布

正文完
 0