如何使用 Node 中的异步函数与数据库交互.js
作者:互联网
异步函数概述
异步函数是JavaScript的一个相对较新的功能(不是特定于Node.js)。对该功能的支持首先登陆 Node.js v7.6 通过对 V8 JavaScript 引擎的更新。由于异步函数严重依赖 Promises,我建议您在继续之前阅读上一篇文章。
我喜欢将异步函数视为两部分:异步和等待。让我们依次看一下每个部分。
async
长期以来,我们已经能够使用函数语句(必须命名)或函数表达式(通常是匿名的)在 JavaScript 中创建函数。
2023 年微服务和容器化将面临什么
DZone 的 2022 年微服务和容器化趋势报告探讨了云架构和设计原则的交集,深入探讨了微服务编排技术、微服务设计模式、容器安全性、服务网格等!
1
function getNumber() { // Function statment
2
return 42;
3
}
4
5
let logNumber = function() { // Function expression
6
console.log(getNumber());
7
}
8
9
logNumber(); // 42
如果在 Node.js 中运行上述脚本,则应看到打印到控制台。42
JavaScript 现在有这些构造的异步对应项。将 new 关键字放在函数语句或表达式之前将返回 AsyncFunction(异步函数)对象。async
async function getNumber() { // Async function statment
2
return 42;
3
}
4
5
let logNumber = async function() { // Async function expression
6
console.log(getNumber());
7
}
8
9
logNumber(); // Promise { 42 }
在 Node.js 中运行此脚本应打印 。如您所见,当调用异步函数时,它们返回的是 promise 而不是返回的实际值!Promise { 42 }
为了使基于异步的脚本在功能上等同于第一个脚本,我们必须按如下方式重写它。
1async function getNumber() { // Async function statment
2
return 42;
3
}
4
5
let logNumber = async function() { // Async function expression
6
getNumber() // returns a promise
7
.then(function(value) {
8
console.log(value);
9
});
10
}
11
12
logNumber(); // 42
现在我们回到记录值。42
正如我们在上一篇文章中看到的 promise 链接一样,如果 async 函数完成而没有错误,那么它返回的 promise 就会被解析。如果函数返回一个值,则它将成为承诺的值。如果抛出错误并且未处理,则承诺将被拒绝,错误将成为承诺的值。
虽然有趣,但返回承诺并不是使异步函数特别的原因。毕竟,我们可以从常规功能中返回承诺。使异步函数特别的是。await
await
运算符仅在异步函数中可用,是魔术发生的地方。这就像在代码上按下暂停按钮,以便它可以等待承诺被解决或拒绝,然后再继续。这是一个称为协程的概念。自从引入生成器函数以来,协程在 JavaScript 中已经可用,但异步函数使它们更容易接近。await
等待不会阻塞主线程。相反,允许当前运行的调用堆栈(直到 为止)完成,以便可以执行回调队列中的其他函数。当承诺被解析或拒绝时,代码的剩余部分将排队等待执行。如果承诺已解析,则返回其值。如果承诺被拒绝,则在主线程上抛出被拒绝的值。await
下面是用于模拟异步 API 的演示。我添加了一些额外的控制台输出来帮助说明正在发生的情况。await
setTimeout
function getRandomNumber() {
2
return new Promise(function(resolve, reject) {
3
setTimeout(function() {
4
const randomValue = Math.random();
5
const error = randomValue > .8 ? true : false;
6
7
if (error) {
8
reject(new Error('Ooops, something broke!'));
9
} else {
10
resolve(randomValue);
11
}
12
}, 2000);
13
});
14
}
15
16
async function logNumber() {
17
let number;
18
19
console.log('before await', number);
20
21
number = await getRandomNumber();
22
23
console.log('after await', number);
24
}
25
26
console.log('before async call');
27
28
logNumber();
29
30
console.log('after async call');
当此脚本在 Node 中运行时.js没有发生错误,输出将如下所示(我在发生两秒延迟的地方添加了一条注释)。
1before async call
2
before await undefined
3
after async call
4
# 2 second delay
5
after await 0.22454453163016597
请注意,之前在等待 0.22454453163016597 之后记录。只有异步函数中的剩余代码被暂停;调用堆栈中的其余同步代码将完成。after async call
如果抛出错误,您将看到我们在上一篇文章中介绍的内容。拒绝可以使用该帖子中提到的方法或使用!UnhandledPromiseRejectionWarning
try…catch
尝试。。。抓住
在本系列的第一篇文章中,我解释了为什么块不适用于异步操作 - 您无法捕获当前调用堆栈之外发生的错误。但是现在我们有异步函数, 可以用于异步操作!try…catch
try…catch
下面是上一个脚本的精简版本,用于捕获异步 API 中发生的错误并改用默认值。
1function getRandomNumber() {
2
return new Promise(function(resolve, reject) {
3
setTimeout(function() {
4
const randomValue = Math.random();
5
const error = randomValue > .8 ? true : false;
6
7
if (error) {
8
reject(new Error('Ooops, something broke!'));
9
} else {
10
resolve(randomValue);
11
}
12
}, 2000);
13
});
14
}
15
16
async function logNumber() {
17
let number;
18
19
try {
20
number = await getRandomNumber();
21
} catch (err) {
22
number = 42;
23
}
24
25
console.log(number);
26
}
27
28
logNumber();
如果运行该脚本的次数足够多,则最终将进入输出。 又工作了,呜呼!42
try…catch
异步循环
除了能够再次使用块之外,我们还可以进行异步循环!在下面的示例中,我使用一个简单的循环,该循环按顺序记录三个值。try…catch
for
function getRandomNumber() {
2
return new Promise(function(resolve, reject) {
3
setTimeout(function() {
4
const randomValue = Math.random();
5
const error = randomValue > .8 ? true : false;
6
7
if (error) {
8
reject(new Error('Ooops, something broke!'));
9
} else {
10
resolve(randomValue);
11
}
12
}, 2000);
13
});
14
}
15
16
async function logNumbers() {
17
for (let x = 0; x < 3; x += 1) {
18
console.log(await getRandomNumber());
19
}
20
}
21
22
logNumbers();
在 Node.js 中运行此脚本,您应该看到每两秒打印到控制台的三个数字。没有第三方库,没有复杂的承诺链,只有一个简单的循环。循环再次工作,耶!
并行执行
显然,异步函数可以轻松执行顺序流,并将标准 JavaScript 构造与异步操作一起使用。但是平行流呢?这就是派上用场的地方。因为它们都返回 promise, 所以可以像任何其他基于 promise 的 API 一样使用它们。Promise.all
Promise.race
await
下面是一个使用 Promise.all 并行获取三个随机数的示例。
1function getRandomNumber() {
2
return new Promise(function(resolve, reject) {
3
setTimeout(function() {
4
const randomValue = Math.random();
5
const error = randomValue > .8 ? true : false;
6
7
if (error) {
8
reject(new Error('Ooops, something broke!'));
9
} else {
10
resolve(randomValue);
11
}
12
}, 2000);
13
});
14
}
15
16
async function logNumbers() {
17
let promises = [];
18
19
promises[0] = getRandomNumber();
20
promises[1] = getRandomNumber();
21
promises[2] = getRandomNumber();
22
23
Promise.all(promises)
24
.then(function(values) {
25
console.log(values);
26
})
27
.catch(function(err) {
28
console.log(err);
29
});
30
}
31
32
logNumbers();
因为如果传入的任何承诺被拒绝,就会拒绝其承诺,因此您可能需要运行脚本几次才能看到打印出的三个随机数。Promise.all
异步函数演示应用
异步函数演示应用由以下四个文件组成。这些文件也可以通过此 Gist 获得。
package.json
:
{
2
"name": "async-functions",
3
"version": "1.0.0",
4
"description": "",
5
"main": "index.js",
6
"scripts": {
7
"test": "echo \"Error: no test specified\" && exit 1"
8
},
9
"keywords": [],
10
"author": "Dan McGhan <dan.mcghan@oracle.com> (https://jsao.io/)",
11
"license": "ISC",
12
"dependencies": {
13
"oracledb": "^1.13.1"
14
}
15
}
这是一个非常基本的文件。唯一的外部依赖项是oracledb。package.json
index.js
:
const oracledb = require('oracledb');
2
const dbConfig = require('./db-config.js');
3
const employees = require('./employees.js');
4
5
async function startApp() {
6
try {
7
await oracledb.createPool(dbConfig);
8
9
let emp = await employees.getEmployee(101);
10
11
console.log(emp);
12
} catch (err) {
13
console.log('Opps, an error occurred', err);
14
}
15
}
16
17
startApp();
node-oracledb 中的所有异步方法都被重载以使用回调函数或承诺。如果回调函数未作为最后一个参数传入,则将返回一个承诺。此版本的 使用带有驱动程序承诺 API 的运算符来创建连接池并提取员工。尽管池是从对 的调用返回的,但此处未引用该池,因为内置池缓存将在 中使用。index.js
await
createPool
employees.js
db-config.js
:
module.exports = {
2
user: 'hr',
3
password: 'oracle',
4
connectString: 'localhost:1521/orcl',
5
poolMax: 20,
6
poolMin: 20,
7
poolIncrement: 0
8
};
该文件用于提供数据库的连接信息。此配置应适用于数据库应用开发 VM,但需要针对其他环境进行调整。db-config.js
index.js
employees.js
:
const oracledb = require('oracledb');
2
3
function getEmployee(empId) {
4
return new Promise(async function(resolve, reject) {
5
let conn; // Declared here for scoping purposes.
6
7
try {
8
conn = await oracledb.getConnection();
9
10
console.log('Connected to database');
11
12
let result = await conn.execute(
13
`select *
14
from employees
15
where employee_id = :emp_id`,
16
[empId],
17
{
18
outFormat: oracledb.OBJECT
19
}
20
);
21
22
console.log('Query executed');
23
24
resolve(result.rows[0]);
25
} catch (err) {
26
console.log('Error occurred', err);
27
28
reject(err);
29
} finally {
30
// If conn assignment worked, need to close.
31
if (conn) {
32
try {
33
await conn.close();
34
35
console.log('Connection closed');
36
} catch (err) {
37
console.log('Error closing connection', err);
38
}
39
}
40
}
41
});
42
}
43
44
module.exports.getEmployee = getEmployee;
这个版本的 employee 模块类似于 promise 版本,因为该函数是作为基于 promise 的 API 编写的——它会立即返回一个异步解析或拒绝的新 promise 实例。主要区别在于,与驱动程序的承诺 API 一起使用,以获取与数据库的连接,使用它执行查询,然后关闭连接。getEmployee
await
一个块用于捕获错误并确保连接以任何一种方式关闭。对我来说,这个版本的模块是该系列中所有模块中最容易阅读的,而且它的代码行数也最少也没有什么坏处。try…catch…finally