alertdialog 自定义样式回调选手_把 Node.js 中的回调转换为 Promise

2023-11-07

// 每日前端夜话 第431篇
// 正文共:2300 字
// 预计阅读时间:7 分钟
0532161ba4def689ea21063fb521a9b7.png

介绍

在几年前,回调是 JavaScript 中实现执行异步代码的唯一方法。回调本身几乎没有什么问题,最值得注意的是“回调地狱”。

在 ES6 中引入了 Promise 作为这些问题的解决方案。最后通过引入  async/await  关键字来提供更好的体验并提高了可读性。

即使有了新的方法,但是仍然有许多使用回调的原生模块和库。在本文中,我们将讨论如何将 JavaScript 回调转换为 Promise。ES6 的知识将会派上用场,因为我们将会使用 展开操作符之类的功能来简化要做的事情。

什么是回调

回调是一个函数参数,恰好是一个函数本身。虽然我们可以创建任何函数来接受另一个函数,但回调主要用于异步操作。

JavaScript 是一种解释性语言,一次只能处理一行代码。有些任务可能需要很长时间才能完成,例如下载或读取大文件等。JavaScript 将这些运行时间很长的任务转移到浏览器或 Node.js 环境中的其他进程中。这样它就不会阻止其他代码的执行。

通常异步函数会接受回调函数,所以完成之后可以处理其数据。

举个例子,我们将编写一个回调函数,这个函数会在程序成功从硬盘读取文件之后执行。

所以需要准备一个名为 sample.txt 的文本文件,其中包含以下内容:

Hello world from sample.txt

然后写一个简单的 Node.js 脚本来读取文件:

const fs = require('fs');

fs.readFile('./sample.txt', 'utf-8', (err, data) => {
    if (err) {
        // 处理错误
        console.error(err);
          return;
    }
    console.log(data);
});

for (let i = 0; i 10; i++) {
    console.log(i);
}

运行代码后将会输出:

0
...
8
9
Hello world from sample.txt

如果这段代码,应该在执行回调之前看到 0..9 被输出到控制台。这是因为 JavaScript 的异步管理机制。在读取文件完毕之后,输出文件内容的回调才被调用。

顺便说明一下,回调也可以在同步方法中使用。例如  Array.sort()  会接受一个回调函数,这个函数允许你自定义元素的排序方式。

接受回调的函数被称为“高阶函数”。

现在我们有了一个更好的回调方法。那么们继续看看什么是 Promise。

什么是 Promise

在 ECMAScript 2015(ES6)中引入了 Promise,用来改善在异步编程方面的体验。顾名思义,JavaScript 对象最终将返回的“值”或“错误”应该是一个 Promise。

一个 Promise 有 3 个状态:

  • Pending(待处理):用来指示异步操作尚未完成的初始状态。
  • Fulfilled(已完成):表示异步操作已成功完成。
  • Rejected(拒绝):表示异步操作失败。

大多数 Promise 最终看起来像这样:

someAsynchronousFunction()
    .then(data => {
        // promise 被完成
        console.log(data);
    })
    .catch(err => {
        // promise 被拒绝
        console.error(err);
    });

Promise 在现代 JavaScript 中非常重要,因为它们与 ECMAScript 2016 中引入的 async/await 关键字一起使用。使用 async / await 就不需要再用回调或 then()catch() 来编写异步代码。

如果要改写前面的例子,应该是这样:

try {
    const data = await someAsynchronousFunction();
} catch(err) {
    // promise 被拒绝
    console.error(err);
}

这看起来很像“一般的”同步 JavaScript。大多数流行的JavaScript库和新项目都把 Promises 与 async/await 关键字放在一起用。

但是,如果你要更新现有的库或遇到旧的代码,则可能会对将基于回调的 API 迁移到基于 Promise 的 API 感兴趣,这样可以改善你的开发体验。

来看一下将回调转换为 Promise 的几种方法。

将回调转换为 Promise

Node.js Promise

大多数在 Node.js 中接受回调的异步函数(例如 fs 模块)有标准的实现方式:把回调作为最后一个参数传递。

例如这是在不指定文本编码的情况下用 fs.readFile() 读取文件的方法:

fs.readFile('./sample.txt', (err, data) => {
    if (err) {
        console.error(err);
          return;
    }
    console.log(data);
});

注意:如果你指定 utf-8 作为编码,那么得到的输出是一个字符串。如果不指定得到的输出是 Buffer

另外传给这个函数的回调应接受 Error,因为它是第一个参数。之后可以有任意数量的输出。

如果你需要转换为 Promise 的函数遵循这些规则,那么可以用 util.promisify ,这是一个原生 Node.js 模块,其中包含对 Promise 的回调。

首先导入ʻutil`模块:

const util = require('util');

然后用 promisify 方法将其转换为 Promise:

const fs = require('fs');
const readFile = util.promisify(fs.readFile);

现在,把新创建的函数用作 promise:

readFile('./sample.txt', 'utf-8')
    .then(data => {
        console.log(data);
    })
    .catch(err => {
        console.log(err);
    });

另外也可以用下面这个示例中给出的 async/await 关键字:

const fs = require('fs');
const util = require('util');

const readFile = util.promisify(fs.readFile);

(async () => {
    try {
        const content = await readFile('./sample.txt', 'utf-8');
        console.log(content);
    } catch (err) {
        console.error(err);
    }
})();

你只能在用 async 创建的函数中使用 await 关键字,这也是为什么要使用函数包装器的原因。函数包装器也被称为立即调用的函数表达式。

如果你的回调不遵循这个特定标准也不用担心。util.promisify() 函数可让你自定义转换是如何发生的。

注意:Promise 在被引入后不久就开始流行了。Node.js 已经将大部分核心函数从回调转换成了基于 Promise 的API。

如果需要用 Promise 处理文件,可以用 Node.js 附带的库(https://nodejs.org/docs/latest-v10.x/api/fs.html#fs_fs_promises_api)。

现在你已经了解了如何将 Node.js 标准样式回调隐含到 Promise 中。从 Node.js 8 开始,这个模块仅在 Node.js 上可用。如果你用的是浏览器或早期版本版本的 Node,则最好创建自己的基于 Promise 的函数版本。

创建你自己的 Promise

让我们讨论一下怎样把回调转为  util.promisify() 函数的 promise。

思路是创建一个新的包含回调函数的 Promise 对象。如果回调函数返回错误,就拒绝带有该错误的Promise。如果回调函数返回非错误输出,就解决并输出 Promise。

先把回调转换为一个接受固定参数的函数的 promise 开始:

const fs = require('fs');

const readFile = (fileName, encoding) => {
    return new Promise((resolve, reject) => {
        fs.readFile(fileName, encoding, (err, data) => {
            if (err) {
                return reject(err);
            }

            resolve(data);
        });
    });
}

readFile('./sample.txt')
    .then(data => {
        console.log(data);
    })
    .catch(err => {
        console.log(err);
    });

新函数 readFile() 接受了用来读取 fs.readFile() 文件的两个参数。然后创建一个新的 Promise 对象,该对象包装了该函数,并接受回调,在本例中为 fs.readFile()

要  reject  Promise 而不是返回错误。所以代码中没有立即把数据输出,而是先 resolve 了Promise。然后像以前一样使用基于 Promise 的 readFile() 函数。

接下来看看接受动态数量参数的函数:

const getMaxCustom = (callback, ...args) => {
    let max = -Infinity;

    for (let i of args) {
        if (i > max) {
            max = i;
        }
    }

    callback(max);
}

getMaxCustom((max) => { console.log('Max is ' + max) }, 10, 2, 23, 1, 111, 20);

第一个参数是 callback 参数,这使它在接受回调的函数中有点与众不同。

转换为 promise 的方式和上一个例子一样。创建一个新的 Promise 对象,这个对象包装使用回调的函数。如果遇到错误,就 reject,当结果出现时将会 resolve

我们的 promise 版本如下:

const getMaxPromise = (...args) => {
    return new Promise((resolve) => {
        getMaxCustom((max) => {
            resolve(max);
        }, ...args);
    });
}

getMaxCustom(10, 2, 23, 1, 111, 20)
    .then(max => console.log(max));

在创建 promise 时,不管函数是以非标准方式还是带有许多参数使用回调都无关紧要。我们可以完全控制它的完成方式,并且原理是一样的。

总结

尽管现在回调已成为 JavaScript 中利用异步代码的默认方法,但 Promise 是一种更现代的方法,它更容易使用。如果遇到了使用回调的代码库,那么现在就可以把它转换为 Promise。

在本文中,我们首先学到了如何 在Node.js 中使用 utils.promisfy() 方法将接受回调的函数转换为 Promise。然后,了解了如何创建自己的 Promise 对象,并在对象中包装了无需使用外部库即可接受回调的函数。这样许多旧 JavaScript 代码可以轻松地与现代的代码库和混合在一起。


640?wx_fmt=gif
640?wx_fmt=png
精彩文章回顾,点击直达

f479c79ec4fc71d4e9a0192f948521aa.png

fae3a78ddeef755da52ce353a562fee9.png

点分享

652552334cc58cd255c9e0d3469b58be.png

点收藏

f998dbda77cd2305d6aad7efeba3ea7d.png

点点赞

d70fc612a43bdaa76abbffbc24e8f4d0.png

点在看

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

alertdialog 自定义样式回调选手_把 Node.js 中的回调转换为 Promise 的相关文章

  • Pyppeteer的使用——爬取京东

    1 Pyppeteer优势 不用像Selenium一样配置浏览器环境 可以直接在页面上进行爬取 爬取的不是页面源码而是已经加载完毕的 显示在浏览器上的页面 可以绕过加密系统 Pyppeteer加载的text 是加载完成后的HTML页面 所有
  • ad如何计算电路板的pin数量_一招教你学会使用AD更改PCB板子尺寸!

    原标题 一招教你学会使用AD更改PCB板子尺寸 使用原理图生成PCB后 Altium Designer会自动生成一块黑色区域 还有一个在禁止布线层的方框 还有两段标注板子大小的线 下面说一下如何更改黑色区域的大小 还有如何精确确定板子尺寸
  • DES密码算法实现(C语言)

    算法介绍 DES算法为密码体制中的对称密码体制 又被称为美国数据加密标准 是1972年美国IBM公司研制的对称密码体制加密算法 明文按64位进行分组 密钥长64位 密钥事实上是56位参与DES运算 第8 16 24 32 40 48 56
  • 【腾讯云TDSQL-C Serverless 产品测评】一文带你了解TDSQL-C Serverless版

    文章目录 前言 腾讯云TDSQL C for MySQL Serverless版介绍 准备工作 1 购买TDSQL C for MySQL Serverless版实例 2 开启数据库外网访问 3 安装测试工具 4 准备测试数据 Server
  • 记录android遇到的SecurityException

    记录android遇到的SecurityException 一 java lang SecurityException getUniqueDeviceId The user 10283 does not meet the requireme
  • List和ArrayList

    List和ArrayList区别 List是一个接口 而ArrayList是List接口的一个实现类 ArrayList类继承并实现了List接口 因此 List接口不能创建实例对象 但是可以为List接口创建一个指向自己的对象引用 而Ar
  • 微信公众号小程序开通方法_微信小程序发布审核大概要多久

    1 如果自己有通过微信公众平台注册认证好微信公众号 那么只需要登录微信公众号账号后在页面左侧找到小程序管理 注册认证小程序账号 注意 公众号账号和小程序账号是独立的两个账号 2 如果自己没有注册认证微信公众号 就可以先到微信公众平台注册个微
  • ubuntu 18.04下virtualbox安装windows虚拟机+增强功能+secureCRT

    先强调一下 我是在Ubuntu里安装windows虚拟机 如果要看如何安装linux虚拟机的话 那么你走错地方了 我一直使用Linux系统做开发的 选择Ubuntu是因为多数常用软件对Ubuntu支持的不错 能少折腾就少折腾 程序员的时间不
  • Qt5.12.3移植rk3399pro笔记

    Qt5 12 3移植到rk3399pro笔记 环境 主机 Ubuntu16 04 目标机 rk3399pro板 x11平台 交叉编译toolchain linux aarch64 gnu 问题描述 我的目标机是debian系统 带lxde桌
  • 【大数据】HiveQL的数据操作

    HiveQL的数据操作 因为 Hive 没有行级别的数据插入 数据更新和删除操作 那么往表中装载数据的唯一途径就是使用一种 大量 的数据装载操作 或者通过其他方式仅仅将文件写入到正确的目录下 1 向管理表中装载数据 LOAD DATA LO
  • SpringFactoriesLoader ServiceLoader区别

    内容简介 IoC 并不仅限于解决模块内类与类之间的依赖耦合问题 其同样适用于模块与模块之间 OSGi 一直致力于这方面的工作 但其实 Java 和 Spring 都提供了对 IoC 的支持 Java 本身提供了一种很简便的方式来支持 IoC
  • 报告

    来源 Prophet 2019年 战略数字化转型的重要性已经不止于IT领域 而影响着全公司的竞争力 企业的相关预算直线攀升 利益相关方所关注的颠覆性技术数量急剧增加 数字化项目开始由首席高管主导 并由相互协作的跨职能团队管理 数字化是整个企
  • 爬虫项目五:最详细的京东商品、评价爬虫、词云展示

    文章目录 前言 一 京东商品信息爬虫 1 分析URL 2 实例化chrome 3 加载完整数据 4 实现翻页 5 解析数据 二 京东商品评价爬虫 1 找到接口 2 分析url 3 解析数据 4 词云 前言 本文内容包含京东商品列表爬虫的详细
  • pycharm启动报错

    1 点击pycharm 报错 2 打开cmd 输入gpedit msc 点击 确定 3 在本地组策略编辑器 选择 Windows设置 安全设置 本地策略 安全选项 用户帐户控制 用于内置管理员帐户的管理员批准模式 4 设置 用户帐户控制 用
  • cuda历史版本和cudnn的下载地址

    cuda历史版本下载地址 https developer nvidia com cuda toolkit archive cudnn下载地址 https developer nvidia com rdp cudnn archive 欢迎大家
  • pthread_mutex_init线程互斥锁的使用

    pthread mutex init 头文件 include
  • Springboot项目中@JsonProperty不生效-如何处理呢?

    转自 Springboot项目中 JsonProperty不生效 如何处理呢 下文笔者讲述SpringBoot中 JsonProperty不生效的相关简介说明 首先笔者将讲述JsonProperty注解的功能简介说明 JsonPropert
  • googlecloud谷歌云的初学体会(1)

    googlecloud谷歌云入门 1 一 纯小白自述 二 云是个什么云 三 装一个软件 资源 服务 四 服务器 爷爷提供服务的电脑 五 PGSQL的安装 六 总结 一 纯小白自述 自己是个小白 仅仅懂得几句sql查询和编程的基础语法 云是啥
  • 第八十七题 UVa12166 Equilibrium Mobile

    A mobile is a type of kinetic sculpture constructed to take advantage of the principle of equilibrium It consists of a n

随机推荐

  • 傻瓜攻略(七)——MATLAB神经网络的保存和调用

    作为科研领域十分重要的计算工具 MATLAB在深度学习方面也一直与时俱进 每一个版本的更新都会引进许多新的机器学习和深度学习案例 下面介绍将训练好的网络进行保存的方法 当再次调用网络时 可以在前一次训练的基础上进一步训练或者直接处理新数据
  • NIPS 2017

    Attention is all you need Author Unit Google Brain Google Research University of Toronto Authors Ashish Vaswani Noam Sha
  • 什么副业可以月赚1万元?做什么副业可以月入上万?

    在当前经济形势下 对于一个普通上班族而言 指望工资生活 日子肯定是过得紧巴巴的 许多人想谋求一份副业收入很正常 那么 在当前社会上 有哪些副业项目 能一个月收入一万多呢 我这里给大家推荐几个 仅供用于调研参考 1 自媒体 也许很多人会说这个
  • 决策树详解(一)

    1 决策树的概念 决策树算法以树状结构表示数据分类的结果 每个决策点实现一个具有离散输出的测试函数 记为分支 决策树的元素有 根节点 非叶子节点 分支 叶节点四种元素 其代表的含义如下图所示 决策树的工作分为两个阶段 1 训练阶段 给定训练
  • 生成以太坊系地址

    代码解析 要首先生成一个新的钱包地址 我们需要导入go ethereum crypto包 该包提供用于生成随机私钥的GenerateKey方法 privateKey err crypto GenerateKey if err nil log
  • 再论KVM超量使用

    转载自 http www sohu com a 111248295 251444 KVM超量使用一直是热门话题 前段时间发的文章 群讨论 虚拟机能否使用32个CPU 又引去了群友的激烈讨论 本文为群友根据自己的经验总结投稿 感谢这位热心的群
  • 华为 ospf 报文及邻居状态有限机

    华为 IP 路由基础 ospf 报文及邻居状态有限机 ospf协议邻居建立 一 ospf的工作机制 建立邻居 发送数据库信息 计算出最短路径 hello报文 用来建立邻居和维护邻居 邻居发现 是自动发现邻居路由器 使用的组播的地址224 0
  • docker 安装

    前提 开发环境为虚拟机Ubuntu 16 04 更换国内镜像 虚拟机是刚刚安装的 需要先更换成国内镜像源 1 首先备份原始源文件 sudo cp etc apt sources list etc apt sources list bak 2
  • RS推荐系统-LSH最近邻查找+MiniHash

    什么是最近邻查找 在推荐系统中 主要分为召回跟排序两个阶段 召回阶段 基于用户画像及场景数据从海量的视频库 百万级别 中将相关度最高的资源检索出来 作为候选集 召回阶段可以通过 粗糙 的方式召回候选item 排序阶段 基于更加精细的特征对候
  • Aspose实现word、excel、ppt转pdf

    1 工具类 AsposeUtil Component Slf4j public class AsposeUtil private static final String WORD doc docx wps wpt txt private s
  • 布隆过滤器及其实现

    简介 Bloom Filter 是由布隆 Burton Howard Bloom 在1970年提出的 loom Filter是一种空间效率很高的随机数据结构 它实际上是由一个很长的二进制向量和一系列随机映射函数组成 布隆过滤器可以用于可以快
  • kubernetes高可用集群安装(二进制安装、v1.20.2版)

    1 前言 之前文章安装 kubernetes 集群 都是使用 kubeadm 安装 然鹅很多公司也采用二进制方式搭建集群 这篇文章主要讲解 如何采用二进制包来搭建完整的高可用集群 相比使用 kubeadm 搭建 二进制搭建要繁琐很多 需要自
  • 修复Unity空白报错问题

    修复Unity空白报错问题 在升级Unity Hub之后 偶然发现Console里有几行空白的报错 看不到任何信息 由于有报错 导致修改代码无法生效 尝试重启项目 重装Unity都完全没效果 而且就算新建一个空白项目 只要添加代码就会立刻报
  • Linux操作系统原理—内核网络协议栈

    前言 本文主要记录 Linux 内核网络协议栈的运行原理 数据报文的封装与分用 封装 当应用程序用 TCP 协议传送数据时 数据首先进入内核网络协议栈中 然后逐一通过 TCP IP 协议族的每层直到被当作一串比特流送入网络 对于每一层而言
  • CASE WHEN 及 SELECT CASE WHEN的用法

    Case具有两种格式 简单Case函数和Case搜索函数 简单Case函数 CASE sex WHEN 1 THEN 男 WHEN 2 THEN 女 ELSE 其他 END Case搜索函数 CASE WHEN sex 1 THEN 男 W
  • 批量保存数据

    批量保存 批量保存 参数为对象集合 br 条件 对象属性与数据库字段完全对应 允许使用大骆驼拼写法 br 2018年4月28日下午5 30 53 throws Exception private String insert00000 Lis
  • BandZIP无广告版(v6.25)安装及禁止联网设置

    安装和设置 1 下载和安装 bandzip版本v6 25以前的都是免费且无广告的 我们从网上找到6 25版本的进行下载安装 双击 如下图进行设置 点击同意并安装 安装过程中取消自动更新 其他设置默认 安装完成后 虽然不会自动更新 但每次使用
  • C# EasyModbus xktComm Modbus 例子

    转载请注明出处 联系我 田工 15118249062 微信同号 当然先要在NuGet按照相应的dll 不是ModbusRTU报文 在RTU报文前面加了4个字节 transactionIdentifier protocolIdentifier
  • 什么是Scrum?Scrum的核心要点和精髓

    有点长 期望你能通过本文彻底了解 Scrum 我们介绍了一个非常有意思且高效的组织模式 特性团队 我们首先介绍了为什么需要特性团队 特性团队的定义 核心价值 优势 可能存在的问题以及带来的成本 接着讲述了特性团队的适用范围 开发新产品 拓展
  • alertdialog 自定义样式回调选手_把 Node.js 中的回调转换为 Promise

    每日前端夜话 第431篇 正文共 2300 字 预计阅读时间 7 分钟 介绍 在几年前 回调是 JavaScript 中实现执行异步代码的唯一方法 回调本身几乎没有什么问题 最值得注意的是 回调地狱 在 ES6 中引入了 Promise 作