JavaScript回调,承诺和异步函数:第1部分

image.png

每日福利:

2015-04-10_866649.jpg

介绍

有很多关于异步编程的讨论,但具体到底是什么?最重要的是我们希望我们的代码是非阻塞的。

可以阻止我们的应用程序的任务包括发出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方法同步打开文件。因此,我们也可以以同步方式编写初始化代码。但这不是打开文件的最佳方式,因为这是一个潜在的阻塞任务。打开文件应该异步完成,以便执行流程可以是连续的。

Node有一个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()

此示例将打印“second”,“third”,然后“first”。setTimeout函数有0延迟并不重要。它是JavaScript中的异步任务,因此它将始终延迟执行以后执行。该firstTask函数可以表示任何异步任务,如打开文件或查询数据库。

让我们的任务按照我们想要的顺序执行的一个解决方案是使用回调函数。

回调函数

要使用回调函数,可以将函数作为参数传递给另一个函数,然后在任务完成时调用该函数。如果您需要有关如何使用高阶函数的入门知识,那么reactivex 有一个您可以尝试的交互式教程。

回调让我们强制任务按顺序执行。当我们拥有依赖于前一任务结果的任务时,他们也会帮助我们。使用回调,我们可以修复我们的最后一个例子,因此它将打印“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函数中需要注意的一点是try/catch ,我们使用if/else语句而不是使用语句来处理错误。catch块将无法捕获从readFile回调返回的错误。

最好在我们的代码中使用错误处理程序来处理由于外部影响而不是编程错误导致的错误。这包括访问文件,连接到数据库或发出HTTP请求。

在修订的代码示例中,我没有包含任何错误处理。如果在任何步骤中发生错误,程序将无法继续。提供有意义的指示会很好。

错误处理很重要的一个示例是,如果我们有一个登录用户的任务。这涉及从表单中获取用户名和密码,查询我们的数据库以查看它是否是有效组合,然后在我们成功时将用户重定向到其仪表板。如果用户名和密码无效,如果我们不告诉它该怎么做,我们的应用程序将停止运行。

更好的用户体验是向用户返回错误消息并允许他们重试登录。在我们的文件示例中,我们可以在回调函数中传递错误对象。这是readFile函数的情况。然后,当我们执行代码时,我们可以添加一个if/else 语句来处理成功的结果和被拒绝的结果。

任务

使用回调方法,编写一个程序,打开用户文件,选择一个用户,然后打开一个帖子文件,打印用户的信息和所有帖子。

摘要

异步编程是我们的代码中使用的一种方法,用于推迟事件以便以后执行。当您处理异步任务时,回调是一种解决方案,用于对我们的任务进行计时,以便它们按顺序执行。

如果我们有多个任务依赖于先前任务的结果,则一种解决方案是使用多个嵌套回调。然而,这可能导致一个被称为“回调地狱”的问题.Promise解决了回调地狱的问题,异步函数让我们以同步的方式编写代码。在本教程的第2部分中,我们将了解它们是什么以及如何在我们的代码中使用它们。

微信关注:JAVA知己,每天更新。

JAVA知己
image

猜你喜欢

转载自blog.csdn.net/feilang00/article/details/86011561
今日推荐