JavaScript回调,承诺和异步功能:第1部分


介绍

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

可能阻止我们的应用程序的任务包括发出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部分中,我们将学习它们是什么以及如何在我们的代码中使用它们。

资源资源

翻译自: https://code.tutsplus.com/tutorials/javascript-callbacks-promises-and-async-functions-part-1--cms-30000

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值