到目前为止,在本系列异步文章中,我们已经介绍了Node.js样式回调,Async模块和promises。在本系列的最后一部分中,我们将学习异步功能(AKA async / await)。对我而言,自Ajax以来,异步功能是JavaScript发生的最激动人心的事情。最终,我们可以以同步方式读取JavaScript代码,而它却始终像以前一样异步执行。
异步功能概述 异步功能是JavaScript的一项相对较新的功能(并非特定于Node.js)。通过对V8 JavaScript引擎的更新,该功能的支持首先在Node.js v7.6中实现。由于异步功能严重依赖于Promises,因此建议您先阅读上一篇文章,然后再继续。
我喜欢将异步功能分为两部分:异步和等待。让我们依次看一下每个部分。
async 在最长的时间内,我们已经能够使用函数语句(必须命名)或函数表达式(通常是匿名的)在JavaScript中创建函数。
function getNumber() { // Function statment return 42; } let logNumber = function() { // Function expression console.log(getNumber()); } logNumber(); // 42
如果您在Node.js中运行上面的脚本,则应该看到已 42 打印到控制台。
JavaScript现在具有这些构造的异步副本。async 在函数语句或表达式之前放置new 关键字将返回AsyncFunction(异步函数)对象
async function getNumber() { // Async function statment return 42; } let logNumber = async function() { // Async function expression console.log(getNumber()); } logNumber(); // Promise { 42 }
在Node.js中运行此脚本应打印Promise { 42 }。如您所见,当异步函数被调用时,它们返回的是promise,而不是返回的实际值!
为了使基于异步的脚本在功能上等同于第一个脚本,我们必须按如下所示对其进行重写。
async function getNumber() { // Async function statment return 42; } let logNumber = async function() { // Async function expression getNumber() // returns a promise .then(function(value) { console.log(value); }); } logNumber(); // 42
现在我们回到记录值 42。
就像我们在前一篇博文中看到的Promise链所看到的那样,如果async函数正确无误地完成,那么它返回的Promise就会得到解决。如果函数返回一个值,则该值将成为promise的值。如果引发了错误并且无法处理错误,则承诺将被拒绝,错误将成为承诺的价值。
尽管很有趣,但返回诺言并不是使异步函数变得特别的原因。毕竟,我们可以只返回常规函数中的promise。使异步功能与众不同的是 await。
await await 操作符是发生魔术的地方,该 操作符仅在异步函数内部可用。这就像点击代码上的“暂停”按钮,以便它可以在继续执行之前等待承诺被解决或拒绝。这是一个称为协程的概念。自引入生成器函数以来,协程已在JavaScript中可用,但异步函数使它们更易于使用。
等待不会阻塞主线程。取而代之的await是,允许当前运行的调用堆栈(直到) 结束,以便可以执行回调队列中的其他功能。当承诺被解决或被拒绝时,代码的其余部分将排队等待执行。如果承诺被解决,则返回其值。如果承诺被拒绝,则拒绝的值将被抛出到主线程上。
下面是一个演示 await 使用 setTimeout 模拟异步API。我添加了一些其他控制台输出,以帮助说明正在发生的事情。
function getRandomNumber() { return new Promise(function(resolve, reject) { setTimeout(function() { const randomValue = Math.random(); const error = randomValue > .8 ? true : false; if (error) { reject(new Error('Ooops, something broke!')); } else { resolve(randomValue); } }, 2000); }); } async function logNumber() { let number; console.log('before await', number); number = await getRandomNumber(); console.log('after await', number); } console.log('before async call'); logNumber();
当此脚本在Node.js中运行而没有发生错误时,输出将类似于以下内容(我在发生两秒钟延迟的位置添加了注释)。
before async call before await undefined after async call # 2 second delay
请注意,after async call 在等待0.22454453163016597之后才记录。异步功能中只有剩余的代码被暂停;调用堆栈中其余的同步代码将完成。
after async call
0.22454453163016597
如果抛出错误,您将看到UnhandledPromiseRejectionWarning 我们在上一篇 文章中介绍的内容。拒绝可以使用该文章中提到的方法或使用 try…catch!
UnhandledPromiseRejectionWarning
try…catch 在本系列的第一篇文章中,我解释了为什么 try…catch 块不能与异步操作一起使用–您无法捕获在当前调用堆栈之外发生的错误。但是现在我们有了异步功能, try…catch 可以用于异步操作了!
这是先前脚本的精简版本,可捕获异步API中发生的错误,并改用默认值。
function getRandomNumber() { return new Promise(function(resolve, reject) { setTimeout(function() { const randomValue = Math.random(); const error = randomValue > .8 ? true : false; if (error) { reject(new Error('Ooops, something broke!')); } else { resolve(randomValue); } }, 2000); }); } async function logNumber() { let number; try { number = await getRandomNumber(); } catch (err) { number = 42; } console.log(number); } logNumber();
如果您运行该脚本足够的时间,您最终将获得 42 输出。 try…catch 再次工作,呜呼!
异步循环 除了能够try…catch 再次使用 块之外,我们还可以执行异步循环!在下面的示例中,我使用了一个简单的 for 循环,该循环连续注销三个值。
function getRandomNumber() { return new Promise(function(resolve, reject) { setTimeout(function() { const randomValue = Math.random(); const error = randomValue > .8 ? true : false; if (error) { reject(new Error('Ooops, something broke!')); } else { resolve(randomValue); } }, 2000); }); } async function logNumbers() { for (let x = 0; x < 3; x += 1) { console.log(await getRandomNumber()); } } logNumbers();
在Node.js中运行此脚本,您应该每两秒钟看到三个数字打印到控制台。没有第三方库,没有复杂的承诺链,只有一个简单的循环。循环再次起作用,是的!
并行执行 显然,异步函数使执行顺序流和将标准JavaScript构造与异步操作结合使用变得容易。但是并行流又如何呢?这是 Promise.all 和 Promise.race 派上用场。因为它们都返回promise, await 所以可以像使用任何其他基于promise的API一样使用它们。
这是一个使用Promise.all并行获取三个随机数的示例。
function getRandomNumber() { return new Promise(function(resolve, reject) { setTimeout(function() { const randomValue = Math.random(); const error = randomValue > .8 ? true : false; if (error) { reject(new Error('Ooops, something broke!')); } else { resolve(randomValue); } }, 2000); }); } async function logNumbers() { let promises = []; 19 promises[0] = getRandomNumber(); promises[1] = getRandomNumber(); promises[2] = getRandomNumber(); Promise.all(promises) .then(function(values) { console.log(values); }) .catch(function(err) { console.log(err); }); }
由于 Promise.all 如果拒绝任何传入的承诺,则拒绝其承诺,因此您可能需要运行脚本几次才能看到打印出的三个随机数。
异步功能演示应用 异步功能演示应用程序由以下四个文件组成。这些文件也可以通过此Gist获得。
package.json:
{ "name": "async-functions", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "Dan McGhan <dan.mcghan@oracle.com> (https://jsao.io/)", "license": "ISC", "dependencies": { "oracledb": "^1.13.1" } }
这是一个非常基本的 package.json 文件。唯一的外部依赖关系是oracledb。
index.js:
const oracledb = require('oracledb'); const dbConfig = require('./db-config.js'); const employees = require('./employees.js'); async function startApp() { try { await oracledb.createPool(dbConfig); let emp = await employees.getEmployee(101); console.log(emp); } catch (err) { console.log('Opps, an error occurred', err); } } startApp();
node-oracledb中的所有异步方法都被重载以与回调函数或promises一起使用。如果没有将回调函数作为最后一个参数传递,则将返回promise。此版本的版本index.js 使用await 操作员和驱动程序的promise API来创建连接池并获取员工。尽管该池是从调用返回的 createPool,但此处未引用它,因为将在中使用 内置池缓存employees.js。
db-config.js:
module.exports = { user: 'hr', password: 'oracle', connectString: 'localhost:1521/orcl', poolMax: 20, poolMin: 20, poolIncrement: 0 };
该 db-config.js文件用于 index.js 提供数据库的连接信息。此配置应与DB App Dev VM一起使用,但需要针对其他环境进行调整。
db-config.js
index.js
employees.js:
const oracledb = require('oracledb'); function getEmployee(empId) { return new Promise(async function(resolve, reject) { let conn; // Declared here for scoping purposes. try { conn = await oracledb.getConnection(); console.log('Connected to database'); let result = await conn.execute( `select * from employees where employee_id = :emp_id`, [empId], { outFormat: oracledb.OBJECT } ); console.log('Query executed'); resolve(result.rows[0]); } catch (err) { console.log('Error occurred', err); reject(err); } finally { // If conn assignment worked, need to close. if (conn) { try { await conn.close(); console.log('Connection closed'); } catch (err) { console.log('Error closing connection', err); } } } }); }
员工模块的此版本与Promise版本相似,因为该 getEmployee 函数被编写为基于Promise的API-它立即返回一个新的Promise实例,该实例被异步解析或拒绝。主要区别是 await 与驱动程序的promise API一起使用来获得与数据库的连接,使用它执行查询,然后关闭连接。
使用一个 try…catch…finally 块来捕获错误并确保以任何一种方式关闭连接。对我来说,该模块的版本是该系列中所有模块中最容易阅读的,它也具有最少的代码行,这也没有什么坏处。
希望您现在对异步函数有了更好的了解,并像我对使用异步函数一样感到兴奋!
原文链接:https://codingdict.com/