Table des matières
回调地狱或末日金字塔
解决方案1:回调命名和模块化
结构
帮助者
执行登录
检查用户名
检查密码
获取用户相关数据
最后的修饰和使用
解决方案 2:Promise
Q简而言之
准备承诺
检查错误
解决方案 3:步骤库
选择哪一个?
模块化:
承诺(Q):
步骤库:
结论
Maison interface Web js tutoriel Node.js : Relever les défis de l'exécution asynchrone

Node.js : Relever les défis de l'exécution asynchrone

Aug 31, 2023 pm 01:45 PM

Node.js : Relever les défis de lexécution asynchrone

Node.js 允许您快速轻松地创建应用程序。但由于其异步特性,可能很难编写可读且可管理的代码。在本文中,我将向您展示一些如何实现这一目标的技巧。


回调地狱或末日金字塔

Node.js 的构建方式强制您使用异步函数。这意味着回调,回调,甚至更多回调。您可能已经看到过,甚至自己编写过这样的代码:

app.get('/login', function (req, res) {
	sql.query('SELECT 1 FROM users WHERE name = ?;', [ req.param('username') ], function (error, rows) {
		if (error) {
			res.writeHead(500);
			return res.end();
		}
		if (rows.length < 1) {
			res.end('Wrong username!');
		} else {
			sql.query('SELECT 1 FROM users WHERE name = ? && password = MD5(?);', [ req.param('username'), req.param('password') ], function (error, rows) {
				if (error) {
					res.writeHead(500);
					return res.end();
				}
				if (rows.length < 1) {
					res.end('Wrong password!');
				} else {
					sql.query('SELECT * FROM userdata WHERE name = ?;', [ req.param('username') ], function (error, rows) {
						if (error) {
							res.writeHead(500);
							return res.end();
						}
						req.session.username = req.param('username');
						req.session.data = rows[0];
						res.rediect('/userarea');
					});
				}
			});
		}
	});
});
Copier après la connexion

这实际上是直接来自我的第一个 Node.js 应用程序的一个片段。如果您在 Node.js 中做过一些更高级的事情,您可能会理解所有内容,但这里的问题是,每次使用某些异步函数时,代码都会向右移动。它变得更难阅读,更难调试。幸运的是,有一些解决方案可以解决这个问题,因此您可以为您的项目选择合适的解决方案。


解决方案1:回调命名和模块化

最简单的方法是命名每个回调(这将帮助您调试代码)并将所有代码拆分为模块。只需几个简单的步骤即可将上面的登录示例变成一个模块。

结构

让我们从一个简单的模块结构开始。为了避免上述情况,当你只是将混乱分成更小的混乱时,让我们将其作为一个类:

var util = require('util');

function Login(username, password) {
	function _checkForErrors(error, rows, reason) {
		
	}
	
	function _checkUsername(error, rows) {
		
	}
	
	function _checkPassword(error, rows) {
		
	}
	
	function _getData(error, rows) {
		
	}
	
	function perform() {
		
	}
	
	this.perform = perform;
}

util.inherits(Login, EventEmitter);
Copier après la connexion

该类由两个参数构造:用户名密码。看示例代码,我们需要三个函数:一个检查用户名是否正确(_checkUsername),另一个检查密码(_checkPassword),还有一个返回用户相关数据(_getData)并通知应用程序登录成功。还有一个 _checkForErrors 帮助器,它将处理所有错误。最后,有一个 perform 函数,它将启动登录过程(并且是类中唯一的公共函数)。最后我们继承EventEmitter来简化该类的使用。

帮助者

_checkForErrors 函数将检查是否发生任何错误或 SQL 查询是否未返回任何行,并发出相应的错误(以及提供的原因):

function _checkForErrors(error, rows, reason) {
	if (error) {
		this.emit('error', error);
		return true;
	}
	
	if (rows.length < 1) {
		this.emit('failure', reason);
		return true;
	}
	
	return false;
}
Copier après la connexion

它还会返回 truefalse,具体取决于是否发生错误。

执行登录

perform 函数只需执行一个操作:执行第一个 SQL 查询(检查用户名是否存在)并分配适当的回调:

function perform() {
	sql.query('SELECT 1 FROM users WHERE name = ?;', [ username ], _checkUsername);
}
Copier après la connexion

我假设您的 SQL 连接可以在 sql 变量中全局访问(只是为了简化,讨论这是否是一个好的实践超出了本文的范围)。这就是这个函数的全部内容。

检查用户名

下一步是检查用户名是否正确,如果正确,则触发第二个查询 - 检查密码:

function _checkUsername(error, rows) {
	if (_checkForErrors(error, rows, 'username')) {
		return false;
	} else {
		sql.query('SELECT 1 FROM users WHERE name = ? && password = MD5(?);', [ username, password ], _checkPassword);
	}
}
Copier après la connexion

与混乱示例中的代码几乎相同,但错误处理除外。

检查密码

这个函数与前一个函数几乎完全相同,唯一的区别是调用的查询:

function _checkPassword(error, rows) {
	if (_checkForErrors(error, rows, 'password')) {
		return false;
	} else {
		sql.query('SELECT * FROM userdata WHERE name = ?;', [ username ], _getData);
	}
}
Copier après la connexion

获取用户相关数据

此类中的最后一个函数将获取与用户相关的数据(可选步骤)并用它触发成功事件:

function _getData(error, rows) {
	if (_checkForErrors(error, rows)) {
		return false;
	} else {
		this.emit('success', rows[0]);
	}
}
Copier après la connexion

最后的修饰和使用

最后要做的事情是导出类。在所有代码后面添加这一行:

module.exports = Login;
Copier après la connexion

这将使 Login 类成为该模块将导出的唯一内容。稍后可以像这样使用它(假设您已将模块文件命名为 login.js 并且它与主脚本位于同一目录中):

var Login = require('./login.js');

...

app.get('/login', function (req, res) {
	var login = new Login(req.param('username'), req.param('password));
	login.on('error', function (error) {
		res.writeHead(500);
		res.end();
	});
	login.on('failure', function (reason) {
		if (reason == 'username') {
			res.end('Wrong username!');
		} else if (reason == 'password') {
			res.end('Wrong password!');
		}
	});
	login.on('success', function (data) {
		req.session.username = req.param('username');
		req.session.data = data;
		res.redirect('/userarea');
	});
	login.perform();
});
Copier après la connexion

这里又多了几行代码,但是代码的可读性增加了,非常明显。此外,该解决方案不使用任何外部库,这使得如果有新人加入您的项目,它会变得完美。

这是第一种方法,让我们继续第二种方法。


解决方案 2:Promise

使用 Promise 是解决此问题的另一种方法。承诺(正如您可以在提供的链接中阅读的那样)“表示从操作的单个完成中返回的最终值”。实际上,这意味着您可以链接调用以压平金字塔并使代码更易于阅读。

我们将使用 NPM 存储库中提供的 Q 模块。

Q简而言之

在开始之前,我先向您介绍一下Q。对于静态类(模块),我们主要使用 Q.nfcall 函数。它帮助我们将遵循 Node.js 回调模式(其中回调的参数是错误和结果)的每个函数转换为 Promise。它的使用方式如下:

Q.nfcall(http.get, options);
Copier après la connexion

它非常像 Object.prototype.call。您还可以使用 Q.nfapply ,它类似于 Object.prototype.apply:

Q.nfapply(fs.readFile, [ 'filename.txt', 'utf-8' ]);
Copier après la connexion

此外,当我们创建 Promise 时,我们使用 then(stepCallback) 方法添加每个步骤,使用 catch(errorCallback) 捕获错误,并使用 done() 结束。

在这种情况下,由于 sql 对象是一个实例,而不是静态类,所以我们必须使用 Q.ninvokeQ.npost ,它们与上面类似。不同之处在于,我们将方法的名称作为字符串传递到第一个参数中,并将我们想要使用的类的实例作为第二个参数传递,以避免方法从实例。

准备承诺

首先要做的是执行第一步,使用Q.nfcallQ.nfapply(使用你更喜欢的,下面没有区别):

var Q = require('q');

...


app.get('/login', function (req, res) {
	Q.ninvoke('query', sql, 'SELECT 1 FROM users WHERE name = ?;', [ req.param('username') ])
});
Copier après la connexion

请注意该行末尾缺少分号 - 函数调用将被链接起来,因此它不能在那里。我们只是像混乱的示例中那样调用 sql.query ,但我们省略了回调参数 - 它由 Promise 处理。

检查用户名

现在我们可以为 SQL 查询创建回调,它将与“厄运金字塔”示例中的回调几乎相同。在 Q.ninvoke 调用后添加以下内容:

.then(function (rows) {
	if (rows.length < 1) {
		res.end('Wrong username!');
	} else {
		return Q.ninvoke('query', sql, 'SELECT 1 FROM users WHERE name = ? && password = MD5(?);', [ req.param('username'), req.param('password') ]);
	}
})
Copier après la connexion

如您所见,我们使用 then 方法附加回调(下一步)。另外,在回调中我们省略了 error 参数,因为我们稍后会捕获所有错误。我们正在手动检查查询是否返回某些内容,如果是,我们将返回下一个要执行的承诺(同样,由于链接而没有分号)。

检查密码

与模块化示例一样,检查密码与检查用户名几乎相同。这应该在最后一次 then 调用之后进行:

.then(function (rows) {
	if (rows.length < 1) {
		res.end('Wrong password!');
	} else {
		return Q.ninvoke('query', sql, 'SELECT * FROM userdata WHERE name = ?;', [ req.param('username') ]);
	}
})
Copier après la connexion

获取用户相关数据

最后一步是将用户数据放入会话中。再一次,回调与混乱的示例没有太大区别:

.then(function (rows) {
	req.session.username = req.param('username');
	req.session.data = rows[0];
	res.rediect('/userarea');
})
Copier après la connexion

检查错误

使用 Promise 和 Q 库时,所有错误均由使用 catch 方法的回调集处理。在这里,无论错误是什么,我们都只发送 HTTP 500,如上面的示例所示:

.catch(function (error) {
	res.writeHead(500);
	res.end();
})
.done();
Copier après la connexion

之后,我们必须调用 done 方法来“确保,如果错误在结束之前没有得到处理,它将被重新抛出并报告”(来自库的 README)。现在我们漂亮的扁平化代码应该如下所示(并且行为就像混乱的代码一样):

var Q = require('q');

...


app.get('/login', function (req, res) {
	Q.ninvoke('query', sql, 'SELECT 1 FROM users WHERE name = ?;', [ req.param('username') ])
	.then(function (rows) {
		if (rows.length < 1) {
			res.end('Wrong username!');
		} else {
			return Q.ninvoke('query', sql, 'SELECT 1 FROM users WHERE name = ? && password = MD5(?);', [ req.param('username'), req.param('password') ]);
		}
	})
	.then(function (rows) {
		if (rows.length < 1) {
			res.end('Wrong password!');
		} else {
			return Q.ninvoke('query', sql, 'SELECT * FROM userdata WHERE name = ?;', [ req.param('username') ]);
		}
	})
	.then(function (rows) {
		req.session.username = req.param('username');
		req.session.data = rows[0];
		res.rediect('/userarea');
	})
	.catch(function (error) {
		res.writeHead(500);
		res.end();
	})
	.done();
});
Copier après la connexion

代码更加简洁,并且比模块化方法涉及的重写更少。


解决方案 3:步骤库

此解决方案与上一个解决方案类似,但更简单。 Q 有点重,因为它实现了整个 Promise 的想法。 Step 库的存在只是为了消除回调地狱。使用起来也更简单,因为您只需调用从模块导出的唯一函数,将所有回调作为参数传递,并使用 this 代替每个回调。因此,可以使用 Step 模块将这个混乱的示例转换成这样:

var step = require('step');

...

app.get('/login', function (req, res) {
	step(
		function start() {
			sql.query('SELECT 1 FROM users WHERE name = ?;', [ req.param('username') ], this);
		},
		function checkUsername(error, rows) {
			if (error) {
				res.writeHead(500);
				return res.end();
			}
			if (rows.length < 1) {
				res.end('Wrong username!');
			} else {
				sql.query('SELECT 1 FROM users WHERE name = ? && password = MD5(?);', [ req.param('username'), req.param('password') ], this);
			}
		},
		function checkPassword(error, rows) {
			if (error) {
				res.writeHead(500);
				return res.end();
			}
			if (rows.length < 1) {
				res.end('Wrong password!');
			} else {
				sql.query('SELECT * FROM userdata WHERE name = ?;', [ req.param('username') ], this);
			}
		},
		function (error, rows) {
			if (error) {
				res.writeHead(500);
				return res.end();
			}
			req.session.username = req.param('username');
			req.session.data = rows[0];
			res.rediect('/userarea');
		}
	);
});
Copier après la connexion

这里的缺点是没有通用的错误处理程序。尽管在一个回调中引发的任何异常都会作为第一个参数传递给下一个回调(因此脚本不会因为未捕获的异常而停止运行),但在大多数情况下,为所有错误使用一个处理程序是很方便的。


选择哪一个?

这很大程度上是个人选择,但为了帮助您选择正确的选择,以下列出了每种方法的优缺点:

模块化:

优点:

  • 没有外部库
  • 有助于提高代码的可重用性

缺点:

  • 更多代码
  • 如果您要转换现有项目,则需要进行大量重写

承诺(Q):

优点:

  • 更少的代码
  • 如果应用于现有项目,只需稍微重写

缺点:

  • 您必须使用外部库
  • 需要一些学习

步骤库:

优点:

  • 易于使用,无需学习
  • 如果转换现有项目,则几乎可以进行复制和粘贴

缺点:

  • 没有通用的错误处理程序
  • 正确缩进 step 函数有点困难

结论

正如您所看到的,Node.js 的异步特性是可以管理的,并且可以避免回调地狱。我个人使用模块化方法,因为我喜欢让我的代码结构良好。我希望这些技巧将帮助您编写更具可读性的代码并更轻松地调试脚本。

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn

Outils d'IA chauds

Undresser.AI Undress

Undresser.AI Undress

Application basée sur l'IA pour créer des photos de nu réalistes

AI Clothes Remover

AI Clothes Remover

Outil d'IA en ligne pour supprimer les vêtements des photos.

Undress AI Tool

Undress AI Tool

Images de déshabillage gratuites

Clothoff.io

Clothoff.io

Dissolvant de vêtements AI

AI Hentai Generator

AI Hentai Generator

Générez AI Hentai gratuitement.

Article chaud

R.E.P.O. Crystals d'énergie expliqués et ce qu'ils font (cristal jaune)
3 Il y a quelques semaines By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. Meilleurs paramètres graphiques
3 Il y a quelques semaines By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. Comment réparer l'audio si vous n'entendez personne
3 Il y a quelques semaines By 尊渡假赌尊渡假赌尊渡假赌
WWE 2K25: Comment déverrouiller tout dans Myrise
4 Il y a quelques semaines By 尊渡假赌尊渡假赌尊渡假赌

Outils chauds

Bloc-notes++7.3.1

Bloc-notes++7.3.1

Éditeur de code facile à utiliser et gratuit

SublimeText3 version chinoise

SublimeText3 version chinoise

Version chinoise, très simple à utiliser

Envoyer Studio 13.0.1

Envoyer Studio 13.0.1

Puissant environnement de développement intégré PHP

Dreamweaver CS6

Dreamweaver CS6

Outils de développement Web visuel

SublimeText3 version Mac

SublimeText3 version Mac

Logiciel d'édition de code au niveau de Dieu (SublimeText3)

Comment créer et publier mes propres bibliothèques JavaScript? Comment créer et publier mes propres bibliothèques JavaScript? Mar 18, 2025 pm 03:12 PM

L'article discute de la création, de la publication et du maintien des bibliothèques JavaScript, en se concentrant sur la planification, le développement, les tests, la documentation et les stratégies de promotion.

Comment optimiser le code JavaScript pour les performances dans le navigateur? Comment optimiser le code JavaScript pour les performances dans le navigateur? Mar 18, 2025 pm 03:14 PM

L'article traite des stratégies pour optimiser les performances JavaScript dans les navigateurs, en nous concentrant sur la réduction du temps d'exécution et la minimisation de l'impact sur la vitesse de chargement de la page.

Que dois-je faire si je rencontre l'impression de code brouillé pour les reçus en papier thermique frontal? Que dois-je faire si je rencontre l'impression de code brouillé pour les reçus en papier thermique frontal? Apr 04, 2025 pm 02:42 PM

Des questions et des solutions fréquemment posées pour l'impression de billets thermiques frontaux pour le développement frontal, l'impression de billets est une exigence commune. Cependant, de nombreux développeurs mettent en œuvre ...

Comment déboguer efficacement le code JavaScript à l'aide d'outils de développeur de navigateur? Comment déboguer efficacement le code JavaScript à l'aide d'outils de développeur de navigateur? Mar 18, 2025 pm 03:16 PM

L'article traite du débogage efficace de JavaScript à l'aide d'outils de développeur de navigateur, de se concentrer sur la définition des points d'arrêt, de l'utilisation de la console et d'analyser les performances.

Comment utiliser les cartes source pour déboguer le code JavaScript minifié? Comment utiliser les cartes source pour déboguer le code JavaScript minifié? Mar 18, 2025 pm 03:17 PM

L'article explique comment utiliser les cartes source pour déboguer JavaScript minifiée en le mappant au code d'origine. Il discute de l'activation des cartes source, de la définition de points d'arrêt et de l'utilisation d'outils comme Chrome Devtools et WebPack.

Comment utiliser efficacement le cadre de collections de Java? Comment utiliser efficacement le cadre de collections de Java? Mar 13, 2025 pm 12:28 PM

Cet article explore une utilisation efficace du cadre de collections de Java. Il met l'accent sur le choix des collections appropriées (liste, set, map, file d'attente) en fonction de la structure des données, des besoins en performances et de la sécurité des threads. Optimisation de l'utilisation de la collection grâce à

TypeScript pour les débutants, partie 2: Types de données de base TypeScript pour les débutants, partie 2: Types de données de base Mar 19, 2025 am 09:10 AM

Une fois que vous avez maîtrisé le didacticiel TypeScript de niveau d'entrée, vous devriez être en mesure d'écrire votre propre code dans un IDE qui prend en charge TypeScript et de le compiler en JavaScript. Ce tutoriel plongera dans divers types de données dans TypeScript. JavaScript a sept types de données: null, non défini, booléen, numéro, chaîne, symbole (introduit par ES6) et objet. TypeScript définit plus de types sur cette base, et ce tutoriel les couvrira tous en détail. Type de données nuls Comme javascript, null en typeScript

Début avec Chart.js: tarte, beignet et graphiques à bulles Début avec Chart.js: tarte, beignet et graphiques à bulles Mar 15, 2025 am 09:19 AM

Ce tutoriel expliquera comment créer des graphiques à tarte, anneaux et bulles à l'aide de chart.js. Auparavant, nous avons appris quatre types de graphiques de graphique. Créer des graphiques à tarte et à anneaux Les graphiques à tarte et les graphiques d'anneaux sont idéaux pour montrer les proportions d'un tout divisé en différentes parties. Par exemple, un graphique à secteurs peut être utilisé pour montrer le pourcentage de lions mâles, de lions féminins et de jeunes lions dans un safari, ou le pourcentage de votes que différents candidats reçoivent lors des élections. Les graphiques à tarte ne conviennent que pour comparer des paramètres ou des ensembles de données uniques. Il convient de noter que le graphique à tarte ne peut pas dessiner des entités avec une valeur nulle car l'angle du ventilateur dans le graphique à tarte dépend de la taille numérique du point de données. Cela signifie toute entité avec une proportion nulle

See all articles