![](https://i-blog.csdnimg.cn/blog_migrate/37afc71692a29e40f817f2d165732fdc.png)
介绍
关于异步编程的讨论很多,但是到底有什么大不了的呢? 重要的是我们希望我们的代码是非阻塞的。
可能阻止我们的应用程序的任务包括发出HTTP请求,查询数据库或打开文件。 某些语言(例如Java)通过创建多个线程来处理此问题。 但是,JavaScript只有一个线程,因此我们需要设计程序,以确保没有任务阻塞流程。
异步编程解决了这个问题。 它使我们能够在以后执行任务,这样我们就不必等待整个程序等待任务就完成。 当我们要确保任务按顺序执行时,它也有帮助。
在本教程的第一部分中,我们将学习同步和异步代码背后的概念,并了解如何使用回调函数解决异步问题。
内容
- 线程数
- 同步与异步
- 回调函数
- 摘要
- 资源资源
线程数
我想让您记住您上次去杂货店购物的时间。 可能有多个收银机开放以结帐客户。 这有助于商店在相同的时间内处理更多的交易。 这是并发的一个例子。
简而言之,并发同时完成了多个任务。 您的操作系统是并发的,因为它同时运行多个进程。 进程是执行环境或正在运行的应用程序的实例。 例如,您的浏览器,文本编辑器和防病毒软件都是计算机上同时运行的所有进程。
应用程序也可以是并发的。 这是通过线程实现的。 我不会太深入,因为这超出了本文的范围。 如果您想深入了解JavaScript的工作原理,建议观看此视频 。
线程是执行代码的进程中的一个单元。 在我们的商店示例中,每个结帐行都是一个线程。 如果商店中只有一条结帐行,那将改变我们处理客户的方式。
您是否曾经排队并且有什么东西可以阻止您的交易? 也许您需要进行价格检查,或者必须去找经理。 当我在邮局尝试运送包裹时,但我没有填写好标签时,收银员要求我在他们继续检查其他客户的同时离开。 准备好后,我将返回到行的最前面以进行检出。
这类似于异步编程的工作方式。 收银员本可以等我的。 有时候他们会。 但是,最好不要排队等候其他客户。 关键是客户不必按照排队的顺序签出。 同样,也不必按照编写顺序执行代码。
同步与异步
考虑我们的代码从上到下顺序执行是很自然的。 这是同步的。 但是,使用JavaScript,某些任务本来就是异步的(例如setTimeout),而有些任务设计为异步的,因为我们提前知道它们会阻塞。
让我们看一个在Node.js中使用文件的实际示例。 如果您想尝试代码示例并需要使用Node.js的入门知识,可以从本教程中找到入门指南 。 在此示例中,我们将打开一个帖子文件并检索其中一个帖子。 然后,我们将打开一个评论文件,并检索该帖子的评论。
这是同步方式:
index.js
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) {
try {
const response = fs.readFileSync(url, 'utf8');
return JSON.parse(response);
} catch (error) {
console.log(error);
}
}
//return an object by id
function getRecord(collection, id) {
return collection.find(function(element){
return element.id == id;
});
}
//return an array of comments for a post
function getCommentsByPost(comments, postId) {
return comments.filter(function(comment){
return comment.postId == postId;
});
}
//initialization code
const posts = loadCollection(postsUrl);
const post = getRecord(posts, "001");
const comments = loadCollection(commentsUrl);
const postComments = getCommentsByPost(comments, post.id);
console.log(post);
console.log(postComments);
db / posts.json
[
{
"id": "001",
"title": "Greeting",
"text": "Hello World",
"author": "Jane Doe"
},
{
"id": "002",
"title": "JavaScript 101",
"text": "The fundamentals of programming.",
"author": "Alberta Williams"
},
{
"id": "003",
"title": "Async Programming",
"text": "Callbacks, Promises and Async/Await.",
"author": "Alberta Williams"
}
]
db / comments.json
[
{
"id": "phx732",
"postId": "003",
"text": "I don't get this callback stuff."
},
{
"id": "avj9438",
"postId": "003",
"text": "This is really useful info."
},
{
"id": "gnk368",
"postId": "001",
"text": "This is a test comment."
}
]
readFileSync
方法同步打开文件。 因此,我们也可以以同步方式编写初始化代码。 但这不是打开文件的最佳方法,因为这是潜在的阻止任务。 打开文件应该异步完成,以便执行流程可以连续进行。
节点具有readFile
方法,可用于异步打开文件。 这是语法:
fs.readFile(url, 'utf8', function(error, data) {
...
});
我们可能很想在此回调函数中返回数据,但我们无法在loadCollection
函数中使用它。 我们的初始化代码也将需要更改,因为我们将没有正确的值来分配给我们的变量。
为了说明问题,让我们看一个简单的例子。 您如何看待以下代码?
function task1() {
setTimeout(function() {
console.log('first');
}, 0);
}
function task2() {
console.log('second');
}
function task3() {
console.log('third');
}
task1();
task2();
task3();
本示例将打印“第二”,“第三”和“第一”。 setTimeout
函数的延迟为0无关紧要。 这是JavaScript中的异步任务,因此始终推迟执行。 firstTask
函数可以表示任何异步任务,例如打开文件或查询我们的数据库。
使任务按所需顺序执行的一种解决方案是使用回调函数。
回调函数
若要使用回调函数,请将一个函数作为参数传递给另一个函数,然后在任务完成时调用该函数。 如果需要有关如何使用高阶函数的入门知识,则reactx可以提供交互式教程供您试用。
回调使我们可以强制任务按顺序执行。 当我们有依赖于先前任务结果的任务时,它们也为我们提供帮助。 使用回调,我们可以修复我们的最后一个示例,这样它将先显示“ first”,“ second”,然后是“ third”。
function first(cb) {
setTimeout(function() {
return cb('first');
}, 0);
}
function second(cb) {
return cb('second');
}
function third(cb) {
return cb('third');
}
first(function(result1) {
console.log(result1);
second(function(result2) {
console.log(result2);
third(function(result3) {
console.log(result3);
});
});
});
而不是在每个函数中打印字符串,而是在回调内部返回值。 执行代码后,我们将打印传递到回调中的值。 这就是我们的readFile
函数使用的。
回到我们的文件示例,我们可以更改loadCollection
函数,以便它使用回调以异步方式读取文件。
function loadCollection(url, callback) {
fs.readFile(url, 'utf8', function(error, data) {
if (error) {
console.log(error);
} else {
return callback(JSON.parse(data));
}
});
}
这就是我们使用回调的初始化代码的样子:
loadCollection(postsUrl, function(posts){
loadCollection(commentsUrl, function(comments){
getRecord(posts, "001", function(post){
const postComments = getCommentsByPost(comments, post.id);
console.log(post);
console.log(postComments);
});
});
});
在loadCollection
函数中要注意的一件事是,我们使用了if/else
语句,而不是使用try/catch
语句来处理错误。 catch块将无法捕获从readFile
回调返回的错误。
优良作法是在我们的代码中包含错误处理程序,以处理由外部影响而不是编程错误引起的错误。 这包括访问文件,连接到数据库或发出HTTP请求。
在修订的代码示例中,我没有包含任何错误处理。 如果在任何一个步骤中发生错误,该程序将不会继续。 提供有意义的说明会很好。
错误处理很重要的一个例子是我们是否有任务登录用户。 这涉及从表单获取用户名和密码,查询我们的数据库以查看它是否是有效的组合,然后,如果我们成功,则将用户重定向到其仪表板。 如果用户名和密码无效,如果我们不告诉用户该怎么做,我们的应用程序将停止运行。
更好的用户体验是将错误消息返回给用户,并允许他们重试登录。在我们的文件示例中,我们可以在回调函数中传递错误对象。 readFile
函数就是这种情况。 然后,当我们执行代码时,我们可以添加一个if/else
语句来处理成功的结果和拒绝的结果。
任务
使用回调方法,编写一个程序,该程序将打开一个用户文件,选择一个用户,然后打开一个帖子文件,并打印该用户的信息及其所有帖子。
摘要
异步编程是我们的代码中使用的一种方法,用于延迟事件以供以后执行。 当您处理异步任务时,回调是一种对我们的任务进行计时以使其顺序执行的解决方案。
如果我们有多个任务依赖于先前任务的结果,则一种解决方案是使用多个嵌套回调。 但是,这可能会导致称为“回调地狱”的问题。 承诺用回调地狱解决了这个问题,异步函数让我们以同步方式编写代码。 在本教程的第2部分中,我们将学习它们是什么以及如何在我们的代码中使用它们。