찾다
웹 프론트엔드JS 튜토리얼Node.js: 비동기 실행 문제 해결

Node.js: 비동기 실행 문제 해결

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');
					});
				}
			});
		}
	});
});

这实际上是直接来自我的第一个 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);

该类由两个参数构造:用户名密码。看示例代码,我们需要三个函数:一个检查用户名是否正确(_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;
}

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

执行登录

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

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

我假设您的 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);
	}
}

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

检查密码

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

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

获取用户相关数据

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

function _getData(error, rows) {
	if (_checkForErrors(error, rows)) {
		return false;
	} else {
		this.emit('success', rows[0]);
	}
}

最后的修饰和使用

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

module.exports = Login;

这将使 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();
});

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

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


解决方案 2:Promise

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

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

Q简而言之

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

Q.nfcall(http.get, options);

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

Q.nfapply(fs.readFile, [ 'filename.txt', 'utf-8' ]);

此外,当我们创建 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') ])
});

请注意该行末尾缺少分号 - 函数调用将被链接起来,因此它不能在那里。我们只是像混乱的示例中那样调用 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') ]);
	}
})

如您所见,我们使用 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') ]);
	}
})

获取用户相关数据

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

.then(function (rows) {
	req.session.username = req.param('username');
	req.session.data = rows[0];
	res.rediect('/userarea');
})

检查错误

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

.catch(function (error) {
	res.writeHead(500);
	res.end();
})
.done();

之后,我们必须调用 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();
});

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


解决方案 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');
		}
	);
});

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


选择哪一个?

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

模块化:

优点:

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

缺点:

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

承诺(Q):

优点:

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

缺点:

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

步骤库:

优点:

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

缺点:

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

结论

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

위 내용은 Node.js: 비동기 실행 문제 해결의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
JavaScript 및 웹 : 핵심 기능 및 사용 사례JavaScript 및 웹 : 핵심 기능 및 사용 사례Apr 18, 2025 am 12:19 AM

웹 개발에서 JavaScript의 주요 용도에는 클라이언트 상호 작용, 양식 검증 및 비동기 통신이 포함됩니다. 1) DOM 운영을 통한 동적 컨텐츠 업데이트 및 사용자 상호 작용; 2) 사용자가 사용자 경험을 향상시키기 위해 데이터를 제출하기 전에 클라이언트 확인이 수행됩니다. 3) 서버와의 진실한 통신은 Ajax 기술을 통해 달성됩니다.

JavaScript 엔진 이해 : 구현 세부 사항JavaScript 엔진 이해 : 구현 세부 사항Apr 17, 2025 am 12:05 AM

보다 효율적인 코드를 작성하고 성능 병목 현상 및 최적화 전략을 이해하는 데 도움이되기 때문에 JavaScript 엔진이 내부적으로 작동하는 방식을 이해하는 것은 개발자에게 중요합니다. 1) 엔진의 워크 플로에는 구문 분석, 컴파일 및 실행; 2) 실행 프로세스 중에 엔진은 인라인 캐시 및 숨겨진 클래스와 같은 동적 최적화를 수행합니다. 3) 모범 사례에는 글로벌 변수를 피하고 루프 최적화, Const 및 Lets 사용 및 과도한 폐쇄 사용을 피하는 것이 포함됩니다.

Python vs. JavaScript : 학습 곡선 및 사용 편의성Python vs. JavaScript : 학습 곡선 및 사용 편의성Apr 16, 2025 am 12:12 AM

Python은 부드러운 학습 곡선과 간결한 구문으로 초보자에게 더 적합합니다. JavaScript는 가파른 학습 곡선과 유연한 구문으로 프론트 엔드 개발에 적합합니다. 1. Python Syntax는 직관적이며 데이터 과학 및 백엔드 개발에 적합합니다. 2. JavaScript는 유연하며 프론트 엔드 및 서버 측 프로그래밍에서 널리 사용됩니다.

Python vs. JavaScript : 커뮤니티, 라이브러리 및 리소스Python vs. JavaScript : 커뮤니티, 라이브러리 및 리소스Apr 15, 2025 am 12:16 AM

Python과 JavaScript는 커뮤니티, 라이브러리 및 리소스 측면에서 고유 한 장점과 단점이 있습니다. 1) Python 커뮤니티는 친절하고 초보자에게 적합하지만 프론트 엔드 개발 리소스는 JavaScript만큼 풍부하지 않습니다. 2) Python은 데이터 과학 및 기계 학습 라이브러리에서 강력하며 JavaScript는 프론트 엔드 개발 라이브러리 및 프레임 워크에서 더 좋습니다. 3) 둘 다 풍부한 학습 리소스를 가지고 있지만 Python은 공식 문서로 시작하는 데 적합하지만 JavaScript는 MDNWebDocs에서 더 좋습니다. 선택은 프로젝트 요구와 개인적인 이익을 기반으로해야합니다.

C/C에서 JavaScript까지 : 모든 것이 어떻게 작동하는지C/C에서 JavaScript까지 : 모든 것이 어떻게 작동하는지Apr 14, 2025 am 12:05 AM

C/C에서 JavaScript로 전환하려면 동적 타이핑, 쓰레기 수집 및 비동기 프로그래밍으로 적응해야합니다. 1) C/C는 수동 메모리 관리가 필요한 정적으로 입력 한 언어이며 JavaScript는 동적으로 입력하고 쓰레기 수집이 자동으로 처리됩니다. 2) C/C를 기계 코드로 컴파일 해야하는 반면 JavaScript는 해석 된 언어입니다. 3) JavaScript는 폐쇄, 프로토 타입 체인 및 약속과 같은 개념을 소개하여 유연성과 비동기 프로그래밍 기능을 향상시킵니다.

JavaScript 엔진 : 구현 비교JavaScript 엔진 : 구현 비교Apr 13, 2025 am 12:05 AM

각각의 엔진의 구현 원리 및 최적화 전략이 다르기 때문에 JavaScript 엔진은 JavaScript 코드를 구문 분석하고 실행할 때 다른 영향을 미칩니다. 1. 어휘 분석 : 소스 코드를 어휘 단위로 변환합니다. 2. 문법 분석 : 추상 구문 트리를 생성합니다. 3. 최적화 및 컴파일 : JIT 컴파일러를 통해 기계 코드를 생성합니다. 4. 실행 : 기계 코드를 실행하십시오. V8 엔진은 즉각적인 컴파일 및 숨겨진 클래스를 통해 최적화하여 Spidermonkey는 유형 추론 시스템을 사용하여 동일한 코드에서 성능이 다른 성능을 제공합니다.

브라우저 너머 : 실제 세계의 JavaScript브라우저 너머 : 실제 세계의 JavaScriptApr 12, 2025 am 12:06 AM

실제 세계에서 JavaScript의 응용 프로그램에는 서버 측 프로그래밍, 모바일 애플리케이션 개발 및 사물 인터넷 제어가 포함됩니다. 1. 서버 측 프로그래밍은 Node.js를 통해 실현되며 동시 요청 처리에 적합합니다. 2. 모바일 애플리케이션 개발은 재교육을 통해 수행되며 크로스 플랫폼 배포를 지원합니다. 3. Johnny-Five 라이브러리를 통한 IoT 장치 제어에 사용되며 하드웨어 상호 작용에 적합합니다.

Next.js (백엔드 통합)로 멀티 테넌트 SAAS 애플리케이션 구축Next.js (백엔드 통합)로 멀티 테넌트 SAAS 애플리케이션 구축Apr 11, 2025 am 08:23 AM

일상적인 기술 도구를 사용하여 기능적 다중 테넌트 SaaS 응용 프로그램 (Edtech 앱)을 구축했으며 동일한 작업을 수행 할 수 있습니다. 먼저, 다중 테넌트 SaaS 응용 프로그램은 무엇입니까? 멀티 테넌트 SAAS 응용 프로그램은 노래에서 여러 고객에게 서비스를 제공 할 수 있습니다.

See all articles

핫 AI 도구

Undresser.AI Undress

Undresser.AI Undress

사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover

AI Clothes Remover

사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool

Undress AI Tool

무료로 이미지를 벗다

Clothoff.io

Clothoff.io

AI 옷 제거제

AI Hentai Generator

AI Hentai Generator

AI Hentai를 무료로 생성하십시오.

뜨거운 도구

MinGW - Windows용 미니멀리스트 GNU

MinGW - Windows용 미니멀리스트 GNU

이 프로젝트는 osdn.net/projects/mingw로 마이그레이션되는 중입니다. 계속해서 그곳에서 우리를 팔로우할 수 있습니다. MinGW: GCC(GNU Compiler Collection)의 기본 Windows 포트로, 기본 Windows 애플리케이션을 구축하기 위한 무료 배포 가능 가져오기 라이브러리 및 헤더 파일로 C99 기능을 지원하는 MSVC 런타임에 대한 확장이 포함되어 있습니다. 모든 MinGW 소프트웨어는 64비트 Windows 플랫폼에서 실행될 수 있습니다.

DVWA

DVWA

DVWA(Damn Vulnerable Web App)는 매우 취약한 PHP/MySQL 웹 애플리케이션입니다. 주요 목표는 보안 전문가가 법적 환경에서 자신의 기술과 도구를 테스트하고, 웹 개발자가 웹 응용 프로그램 보안 프로세스를 더 잘 이해할 수 있도록 돕고, 교사/학생이 교실 환경 웹 응용 프로그램에서 가르치고 배울 수 있도록 돕는 것입니다. 보안. DVWA의 목표는 다양한 난이도의 간단하고 간단한 인터페이스를 통해 가장 일반적인 웹 취약점 중 일부를 연습하는 것입니다. 이 소프트웨어는

SecList

SecList

SecLists는 최고의 보안 테스터의 동반자입니다. 보안 평가 시 자주 사용되는 다양한 유형의 목록을 한 곳에 모아 놓은 것입니다. SecLists는 보안 테스터에게 필요할 수 있는 모든 목록을 편리하게 제공하여 보안 테스트를 더욱 효율적이고 생산적으로 만드는 데 도움이 됩니다. 목록 유형에는 사용자 이름, 비밀번호, URL, 퍼징 페이로드, 민감한 데이터 패턴, 웹 셸 등이 포함됩니다. 테스터는 이 저장소를 새로운 테스트 시스템으로 간단히 가져올 수 있으며 필요한 모든 유형의 목록에 액세스할 수 있습니다.

SublimeText3 Mac 버전

SublimeText3 Mac 버전

신 수준의 코드 편집 소프트웨어(SublimeText3)

메모장++7.3.1

메모장++7.3.1

사용하기 쉬운 무료 코드 편집기