详解JavaScript异步与回调

2023-05-16

一、前言
在学习本文内容之前,我们必须要先了解异步的概念,首先要强调的是异步和并行有着本质的区别

  • 并行,一般指并行计算,是说同一时刻有多条指令同时被执行,这些指令可能执行于同一CPU的多核上,或者多个CPU上,或者多个物理主机甚至多个网络中。

  • 同步,一般指按照预定的顺序依次执行任务,只有当上一个任务完成后,才开始执行下一个任务。

  • 异步,与同步相对应,异步指的是让CPU暂时搁置当前任务,先处理下一个任务,当收到上个任务的回调通知后,再返回上个任务继续执行,整个过程无需第二个线程参与。

也许用图片的方式解释并行、同步和异步更为直观,假设现在有A、B两个任务需要处理,使用并行、同步和异步的处理方式会分别采用如下图所示的执行方式:
在这里插入图片描述

二、异步函数
JavaScript为我们提供了许多异步的函数,这些函数允许我们方便的执行异步任务,也就是说,我们现在开始执行一个任务(函数),但任务会在稍后完成,具体完成时间并不清楚。

例如,setTimeout函数就是一个非常典型的异步函数,此外,fs.readFile、fs.writeFile同样也是异步函数。

我们可以自己定义一个异步任务的案例,例如自定义一个文件复制函数copyFile(from,to):

const fs = require('fs')

function copyFile(from, to) {
    fs.readFile(from, (err, data) => {
        if (err) {
            console.log(err.message)
            return
        }
        fs.writeFile(to, data, (err) => {
            if (err) {
                console.log(err.message)
                return
            }
            console.log('Copy finished')
        })
    })
}

函数copyFile首先从参数from读取文件数据,随后将数据写入参数to指向的文件。

我们可以像这样调用copyFile:

copyFile('./from.txt','./to.txt')//复制文件

如果这个时候,copyFile(…)后面还有其他代码,那么程序不会等待copyFile执行结束,而是直接向下执行,文件复制任务何时结束,程序并不关心。

copyFile('./from.txt','./to.txt')
//下面的代码不会等待上面的代码执行结束
...

执行到这里,好像一切还都是正常的,但是,如果我们在copyFile(…)函数后,直接访问文件./to.txt中的内容会发生什么呢?

这将不会读到复制过来的内容,就行这样:

copyFile('./from.txt','./to.txt')
fs.readFile('./to.txt',(err,data)=>{
    ...
})

如果在执行程序之前,./to.txt文件还没有创建,将得到如下错误:

PS E:\Code\Node\demos\03-callback> node .\index.js
finished
Copy finished
PS E:\Code\Node\demos\03-callback> node .\index.js
错误:ENOENT: no such file or directory, open 'E:\Code\Node\demos\03-callback\to.txt'
Copy finished

即使./to.txt存在,也无法读取其中复制的内容。

造成这种现象的原因是:copyFile(…)是异步执行的,程序执行到copyFile(…)函数后,并不会等待其复制完毕,而是直接向下执行,从而导致出现文件./to.txt不存在的错误,或者文件内容为空错误(如果提前创建文件)。

三、回调函数
异步函数的具体执行结束的时间是不能确定的,例如readFile(from,to)函数的执行结束时间大概率取决于文件from的大小。

那么,问题在于我们如何才能准确的定位copyFile执行结束,从而读取to文件中的内容呢?

这就需要使用回调函数,我们可以修改copyFile函数如下:

function copyFile(from, to, callback) {
    fs.readFile(from, (err, data) => {
        if (err) {
            console.log(err.message)
            return
        }
        fs.writeFile(to, data, (err) => {
            if (err) {
                console.log(err.message)
                return
            }
            console.log('Copy finished')
            callback()//当复制操作完成后调用回调函数
        })
    })
}

这样,我们如果需要在文件复制完成后,立即执行一些操作,就可以把这些操作写入回调函数中:

function copyFile(from, to, callback) {
    fs.readFile(from, (err, data) => {
        if (err) {
            console.log(err.message)
            return
        }
        fs.writeFile(to, data, (err) => {
            if (err) {
                console.log(err.message)
                return
            }
            console.log('Copy finished')
            callback()//当复制操作完成后调用回调函数
        })
    })
}

copyFile('./from.txt', './to.txt', function () {
    //传入一个回调函数,读取“to.txt”文件中的内容并输出
    fs.readFile('./to.txt', (err, data) => {
        if (err) {
            console.log(err.message)
            return
        }
        console.log(data.toString())
    })
})

如果,你已经准备好了./from.txt文件,那么以上代码就可以直接运行:

PS E:\Code\Node\demos\03-callback> node .\index.js
Copy finished

这种编程方式被称为“基于回调”的异步编程风格,异步执行的函数应当提供一个回调参数用于在任务结束后调用。

这种风格在JavaScript编程中普遍存在,例如文件读取函数fs.readFile、fs.writeFile都是异步函数。

四、回调的回调
回调函数可以准确的在异步工作完成后处理后继事宜,如果我们需要依次执行多个异步操作,就需要嵌套回调函数。

案例场景:依次读取文件A和文件B

代码实现:

fs.readFile('./A.txt', (err, data) => {
    if (err) {
        console.log(err.message)
        return
    }
    console.log('读取文件A:' + data.toString())
    fs.readFile('./B.txt', (err, data) => {
        if (err) {
            console.log(err.message)
            return
        }
        console.log("读取文件B:" + data.toString())
    })
})

执行效果:

PS E:\Code\Node\demos\03-callback> node .\index.js
读取文件A:xxx

读取文件B:xxx
http://t.csdn.cn/H1faI

通过回调的方式,就可以在读取文件A之后,紧接着读取文件B。

如果我们还想在文件B之后,继续读取文件C呢?这就需要继续嵌套回调:

fs.readFile('./A.txt', (err, data) => {//第一次回调
    if (err) {
        console.log(err.message)
        return
    }
    console.log('读取文件A:' + data.toString())
    fs.readFile('./B.txt', (err, data) => {//第二次回调
        if (err) {
            console.log(err.message)
            return
        }
        console.log("读取文件B:" + data.toString())
        fs.readFile('./C.txt',(err,data)=>{//第三次回调
            ...
        })
    })
})

也就是说,如果我们想要依次执行多个异步操作,需要多层嵌套回调,这在层数较少时是行之有效的,但是当嵌套次数过多时,会出现一些问题。

回调的约定

实际上,fs.readFile中的回调函数的样式并非个例,而是JavaScript中的普遍约定。我们日后会自定义大量的回调函数,也需要遵守这种约定,形成良好的编码习惯。

约定是:

callback 的第一个参数是为 error 而保留的。一旦出现 error,callback(err) 就会被调用。
第二个以及后面的参数用于接收异步操作的成功结果。此时 callback(null, result1, result2,…) 就会被调用。
基于以上约定,一个回调函数拥有错误处理和结果接收两个功能,例如fs.readFile(‘…’,(err,data)=>{})的回调函数就遵循了这种约定。

五、回调地狱
如果我们不深究的话,基于回调的异步方法处理似乎是相当完美的处理方式。问题在于,如果我们有一个接一个 的异步行为,那么代码就会变成这样:

fs.readFile('./a.txt',(err,data)=>{
    if(err){
        console.log(err.message)
        return
    }
    //读取结果操作
    fs.readFile('./b.txt',(err,data)=>{
        if(err){
            console.log(err.message)
            return
        }
        //读取结果操作
        fs.readFile('./c.txt',(err,data)=>{
            if(err){
                console.log(err.message)
                return
            }
            //读取结果操作
            fs.readFile('./d.txt',(err,data)=>{
                if(err){
                    console.log(err.message)
                    return
                }
                ...
            })
        })
    })
})

以上代码的执行内容是:

读取文件a.txt,如果没有发生错误的话;
读取文件b.txt,如果没有发生错误的话;
读取文件c.txt,如果没有发生错误的话;
读取文件d.txt,…
随着调用的增加,代码嵌套层级越来越深,包含越来越多的条件语句,从而形成不断向右缩进的混乱代码,难以阅读和维护。

我们称这种不断向右增长(向右缩进)的现象为“回调地狱”或者“末日金字塔”!

fs.readFile('a.txt',(err,data)=>{
    fs.readFile('b.txt',(err,data)=>{
        fs.readFile('c.txt',(err,data)=>{
            fs.readFile('d.txt',(err,data)=>{
                fs.readFile('e.txt',(err,data)=>{
                    fs.readFile('f.txt',(err,data)=>{
                        fs.readFile('g.txt',(err,data)=>{
                            fs.readFile('h.txt',(err,data)=>{
                                ...
                                /*
								  通往地狱的大门
								  ===>
                                */
                            })
                        })
                    })
                })
            })
        })
    })
})

虽然以上代码看起来相当规整,但是这只是用于举例的理想场面,通常业务逻辑中会有大量的条件语句、数据处理操作等代码,从而打乱当前美好的秩序,让代码变的难以维护。

幸运的是,JavaScript为我们提供了多种解决途径,Promise就是其中的最优解。

六、总结
本文主要介绍了异步和回调的基本概念,二者是JavaScript的核心内容,需要所有热爱JS的小伙伴深入了解。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

详解JavaScript异步与回调 的相关文章

  • Java中a++与++a的理解

    在编程中我们都熟知 a 43 43 和 43 43 a 两者都是原来的值自身 43 1 xff0c 只不过是前者先进行值得使用再 43 1 xff0c 后者先进行 43 1再使用新的值 xff0c 如下 xff1a int a 61 1 i
  • 面试那些事(一)

    最近裸辞了 xff0c 就觉得解脱了好嗨哦 xff01 终于不要再看到领导丑恶的嘴脸 xff01 终于可以不要再逼着加班啦 xff01 终于周末可以好好的睡一觉了 xff01 本来计划的是找好之后再离职 可是发现根本就没时间去准备 xff0
  • 能ping通,不能ssh登录

    宿主机 ping VMware Linux虚拟机能通 xff0c 但是不能ssh登录 当你试了所有方法都不行时 xff0c linux主机网卡改一个IP地址就好了 xff0c 例如10 0 0 1 10 0 0 2 原因是 Linux网卡
  • docker安装软件时出现:报错:E: You don‘t have enough free space in /var/cache/apt/archives/.

    背景 xff1a 在linux系统下安装了一个docker容器 xff0c 拉取一个debian系统后在系统里使用apt get install进行安装文件 问题 xff1a 报错 xff1a E You don 39 t have eno
  • C语言总结

    1 简述C C语言不但执行效率高而且可移植性好 xff0c 可以用来开发应用软件 驱动 操作系统等 2 第一个C程序 include lt stdio h gt int main printf 34 Hello World 34 retur
  • VNC 1.1 窗口大小修改

    编辑vncserver 文件 vi usr bin vncserver 找到 geometry 61 34 1024x768 34 按 i 修改 按 wq 保存 重启vnc服务即可 PS 不会重启只能一一kill 掉 vncserver k
  • 《Java核心技术》卷1——学习笔记(1)

    第三章的基本语法 1 类名命名规范为骆驼命名法 xff0c 即首字母大写 2 源代码为 java文件 xff0c 编译后字节码文件为 class 控制台先用javac name java命令编译源文件 xff0c 然后用java name运
  • Ubuntu 18.04 install docker-ce(community)

    Ubuntu 18 04 install docker ce community 1 Older versions of Docker were called docker docker io or docker engine If the
  • 三维模型特征提取方法概述

    点击上方 计算机视觉工坊 xff0c 选择 星标 干货第一时间送达 作者I 开拓者5号 64 CSDN 编辑I 3D视觉开发者社区 一 三维特征提取概述 三维特征提取是模式识别中最基本的研究内容之一 xff0c 可以有效地缓解模式识别领域经
  • webpack入门到进阶(七)- devtool

    webpack配置devtool 此选项控制是否生成 xff0c 以及如何生成 source map 一 xff0c 为什么要控制source map的生成 xff1f 我们在开发的过程中 xff0c 难免会遇到项目运行的报错信息 xff0
  • Unix Shell编程——将命令输出结果保存到变量中

    将命令输出结果保存到变量中 文章引用 xff1a http blog csdn net csfreebird article details 7978699 reply xff11 xff0e 两种实现语法 var 61 命令 var 61
  • 真-全局代理原理细谈

    全局代理 以下讨论仅针对windows 起因 最近有个朋友问我当我们的代理软件 xff08 v2rayn xff09 设置成全局代理后 比如自己写的java程序会不会受代理的影响 扩展一下也可以理解成这里的全局代理是不是真的是全局的 探究
  • 跨窗口浏览器通信方式实现交互

    1 需求背景 新老系统交互 xff0c 从新系统页面跳转到老系统页面后 xff0c 老系统页面关闭 xff0c 新系统页面需要同步刷新 2 1 1 解决方案 1 老系统代码使用window opener实现窗口通信 var msgData
  • 《Reinforcement Learning: An Introduction》强化学习导论原文翻译17.1 广义价值函数和辅助任务

    在本书的过程中 xff0c 我们的价值函数概念变得非常普遍 在异策略 xff08 off policy xff09 学习中 xff0c 我们允许在任意目标策略下定义价值函数 然后在12 8节中 xff0c 我们将折扣一般化为终止函数 xff
  • Cannot load command parameter [robot_description]解决方法

    在github上下载一个ros仿真小车 xff0c 运行时 Invalid tag Invalid tag Cannot load command parameter robot description 参考 https wiki ros
  • 【解决】cannot connect to X server

    该问题常出现在Linux跑程序时 xff0c 含图像处理的程序中 这个原因是 xff1a X server是Linux系统上提供图形用户界面的服务程序 当客户端主机Client访问服务器Server上的图形程序时 xff0c 需要Serve
  • 匹配问题: 匈牙利算法 、最优指派、相等子图、库恩—曼克莱斯 (Kuhn-Munkres) 算法

    图 amp 网络系列博文 xff1a 1 图与网络模型及方法 xff1a 图与网络的基本概念 2 图 amp 网络模型应用 最短路径问题 3 树 xff1a 基本概念与最小生成树 4 匹配问题 xff1a 匈牙利算法 最优指派 相等子图 5
  • JavaEE之 IntelliJ IDEA +Tomcat配置JavaEE开发环境

    一 xff0c Tomcat的下载安装 xff1a 1 1 官方下载地址 xff1a https tomcat apache org 尽量选择exe程序安装 xff0c 压缩包第一次没启动起来 xff0c 而且tomcat11版本老是提示版
  • 树莓派VNC连接失败,认证界面无法输入用户名

    文章目录 问题背景思考 xff1a 解决方法 xff1a 第一步 xff1a 使用命令新建桌面 xff1a 第二步 xff1a vnc客户端连接时 xff0c 并且指定桌面号连接成功 xff01 优化方案 xff1a 参考引用 问题背景 由
  • Linux安装Docker详细教程

    文章目录 Docker架构环境说明安装步骤阿里云镜像加速Docker底层原理 Docker架构 镜像 xff08 image xff09 Docker 镜像 xff08 Image xff09 就是一个只读的模板 镜像可以用来创建 Dock

随机推荐

  • 利用Xmanager-Passive运行Xwindow

    需求 不安装X Window System 要通过Xwindows方式运行软件 1 运行Xmanager Passive 右下角查看passive监听的端口 2 命令行运行 61 后面的IP为本地网络地址 0 0为xmanager 监听端口
  • javaWeb中遇到的问题(Artifact second:war exploded: Error during artifact deployment. See server log for .)

    错误提示 xff1a Artifact second war exploded Error during artifact deployment See server log for details 这错误提示一直让我以为是 配置tomca
  • 计算机视觉、图像处理顶会顶刊历年论文链接

    CVPR ICCV ECCV IJCAI AAAI等计算机视觉 图像处理顶会顶刊历年论文链接 在本文中只列出近年的论文网址 xff0c 之前的论文的网址可根据地址栏的参数来进行改变 例 http openaccess thecvf com
  • 程序员养身指南

    皇帝内经 素问 阴阳应象大论篇第五 原文 xff1a 黄帝曰 xff1a 阴阳者 xff0c 天地之道也 xff0c 万物之纲纪 xff0c 变化之父母 xff0c 生杀之本始 xff0c 神明之府也 治病必求于本 故积阳为天 xff0c
  • Devstack- openstack 自动化安装整理

    Devstack openstack 自动化安装整理 openstack的一种自动化安装方式 xff0c 虚拟机系统 xff1a ubuntu16 04 xff0c 单节点安装 1 配置pip源 OpenStack大部分项目是python项
  • Towards Adversarially Robust Object Detection 论文笔记

    前言 许多工作证明分类器在面对对抗攻击 xff08 adversarial attack xff09 时是非常脆弱的 xff0c 比如有一种对抗样本 xff0c 它只对原图进行很轻微地修改 xff0c 但是在视觉上与原图相比是完全不同的 因
  • Linux/Ubuntu环境搭建(二):创建添加新磁盘、搭建Samba服务器

    本文将介绍 如何在虚拟机ubuntu上创建添加一个新磁盘 搭建samba服务器 1 添加新磁盘 在VMware安装Ubuntu系统过程中 xff0c 我们已经创建了一个磁盘了 xff0c 但这个磁盘是跟系统绑定在一起的 xff0c 以后重装
  • Linux 故障排查-测试网络端口连通性

    1 telnet 方法 telnet 协议是 TCP IP 协议族中的一员 xff0c 是 Internet 远程登陆服务的标准协议和主要方式 它为用户提供了在本地计算机上完成远程主机工作的能力 因此我们可以使用telnet 来测试远程机器
  • 基于Redis的布隆过滤器的实现

    项目简介 包含一个基于Redis的布隆过滤器的实现 xff0c 以及应用到Scrapy中的Demo 地址 xff1a BloomFilterRedis 布隆过滤器 网上有很多介绍 xff0c 推荐 数学之美 xff0c 介绍的很详尽 xff
  • 如何利用vs2010(适用其他版本)创建一个c语言程序

    如何利用vs2010创建你的第一个c语言程序 1 打开vs 2010 xff0c 选中新建项目 2 选中win32控制台应用程序 xff0c 起个项目名 不加 c xff0c 自定义路径 3 下一步 4 选中空项目 xff0c 下一步 5
  • Django之后台上传图片(二十二)

    上传图片 在python中进行图片操作 xff0c 需要安装包PIL pip install Pillow 61 61 3 4 1 在Django中上传图片包括两种方式 xff1a 在管理页面admin中上传图片 自定义form表单中上传图
  • #华为mate8公开版解bl锁避免踩雷的事项

    华为mate8公开版解bl锁避免踩雷的注意事项 最近一个朋友 xff08 64 djkaguya xff09 出于某些原因需要将他此型号的备用机解bl锁来迁移服务 xff0c 此篇文章用来记述他在折腾过程中总结出的注意事项 整体流程 1 华
  • qt 修改背景颜色 的几种方法

    按钮方式切换一种颜色 span class token keyword void span MainWindow span class token operator span span class token operator span s
  • 什么是底层驱动程序

    底层驱动是程序以访问底层硬件的形式实现人机交互 xff0c 驱动程序和应用程序之间需要实现相应的信息交互 xff0c 一方面 xff0c 应用程序通过对驱动程序发送相应的指令 xff0c 实现 硬件控制的动作指令 xff0c 另一 方面 x
  • Spring Security 401 问题解决

    背景 xff1a 微服务接口调用的时候报错 xff0c 原来有一个rest服务用的不多 xff0c 平时用的都是一些基础的服务 xff0c 然后客户需要我们开放一个外部接口给他们 xff0c 然后我寻思着就在这里面写接口 然后调用的时候就报
  • ubuntu源不可用

    如果使用ubuntu系统时 xff0c 发现之前的源不可用 xff0c 使用命令 sudo gedit span class token operator span etc span class token operator span ap
  • keras UpSampling2D/3D

    UpSampling2D 看tf文档中的解析 xff1a tf keras layers UpSampling2D xff1a Upsampling layer for 2D inputs 在一个2D输入中进行上采样操作 xff0c 其实就
  • C#winform实现窗口及窗口内容自动缩放(代码已封装)

    c 窗体实现等比例缩小放大 代码如下 原出处为某问答中看到的 摘录下来 以免遗忘 原文地址已附上 如有侵权请联系我 本文仅供学习交流 按我个人理解 setControl函数应当使用多线程加速 本人只是新手 就不画蛇添足了 欢迎各位大佬补正
  • 在移植boa服务器的过程中出现boa:not found问题以及移植cgic库编译时出现arm-linux-gcc-g命令未找到错误

    1 嵌入式web服务器移植的过程在网上很多资源的 xff0c 我在这就不再赘述了 xff0c 我就简单说下一个对于新手来说很头疼的问题 xff01 xff01 移植boa服务器的时候 xff0c 所有的编译工作以及移植工作全部做完了 xff
  • 详解JavaScript异步与回调

    一 前言 在学习本文内容之前 xff0c 我们必须要先了解异步的概念 xff0c 首先要强调的是异步和并行有着本质的区别 并行 xff0c 一般指并行计算 xff0c 是说同一时刻有多条指令同时被执行 xff0c 这些指令可能执行于同一CP