面试官:请实现一个通用函数把 callback 转成 promise

1. 前言

大家好,我是若川。最近组织了源码共读活动,感兴趣的可以加我微信 ruochuan12 参与,或者在公众号:若川视野,回复"源码"参与,每周大家一起学习200行左右的源码,共同进步。已进行三个月了,很多小伙伴表示收获颇丰。

想学源码,极力推荐之前我写的《学习源码整体架构系列》 包含jQueryunderscorelodashvuexsentryaxiosreduxkoavue-devtoolsvuex4koa-composevue-next-releasevue-thiscreate-vue玩具vite等10余篇源码文章。

本文仓库 remote-git-tags-analysis,求个star^_^[1]

我们经常会在本地git仓库切换tags,或者git仓库切换tags。那么我们是否想过如果获取tags呢。本文就是学习 remote-git-tags 这个22行代码的源码库。源码不多,但非常值得我们学习。

阅读本文,你将学到:

1. Node 加载采用什么模块
2. 获取 git 仓库所有 tags 的原理
3. 学会调试看源码
4. 学会面试高频考点 promisify 的原理和实现
5. 等等

刚开始先不急着看上千行、上万行的源码。源码长度越长越不容易坚持下来。看源码讲究循序渐进。比如先从自己会用上的百来行的开始看。

我之前在知乎上回答过类似问题。

一年内的前端看不懂前端框架源码怎么办?

简而言之,看源码

循序渐进
借助调试
理清主线
查阅资料
总结记录

2. 使用

import remoteGitTags from 'remote-git-tags';console.log(await remoteGitTags('https://github.com/lxchuan12/blog.git'));
//=> Map {'3.0.5' => '6020cc35c027e4300d70ef43a3873c8f15d1eeb2', …}

3. 源码

Get tags from a remote Git repo

这个库的作用是:从远程仓库获取所有标签。

原理:通过执行 git ls-remote --tags repoUrl (仓库路径)获取 tags

应用场景:可以看有哪些包依赖的这个包。npm 包描述信息[2]

其中一个比较熟悉的是npm-check-updates[3]

npm-check-updates 将您的 package.json 依赖项升级到最新版本,忽略指定的版本。

还有场景可能是 github 中获取所有 tags 信息,切换 tags 或者选定 tags 发布版本等,比如微信小程序版本。

看源码前先看 package.json 文件。

3.1 package.json

// package.json
{// 指定 Node 以什么模块加载,缺省时默认是 commonjs"type": "module","exports": "./index.js",// 指定 nodejs 的版本"engines": {"node": "^12.20.0 || ^14.13.1 || >=16.0.0"},"scripts": {"test": "xo && ava"}
}

众所周知,Node 之前一直是 CommonJS 模块机制。Node 13 添加了对标准 ES6 模块的支持。

告诉 Node 它要加载的是什么模块的最简单的方式,就是将信息编码到不同的扩展名中。如果是 .mjs 结尾的文件,则 Node 始终会将它作为 ES6 模块来加载。如果是 .cjs 结尾的文件,则 Node 始终会将它作为 CommonJS 模块来加载。

对于以 .js 结尾的文件,默认是 CommonJS 模块。如果同级目录及所有目录有 package.json 文件,且 type 属性为module 则使用 ES6 模块。type 值为 commonjs 或者为空或者没有 package.json 文件,都是默认 commonjs 模块加载。

关于 Node 模块加载方式,在《JavaScript权威指南第7版》16.1.4 Node 模块 小节,有更加详细的讲述。此书第16章都是讲述Node,感兴趣的读者可以进行查阅。

3.2 调试源码

# 推荐克隆我的项目,保证与文章同步,同时测试文件齐全
git clone https://github.com/lxchuan12/remote-git-tags-analysis.git
# npm i -g yarn
cd remote-git-tags && yarn
# VSCode 直接打开当前项目
# code .# 或者克隆官方项目
git clone https://github.com/sindresorhus/remote-git-tags.git
# npm i -g yarn
cd remote-git-tags && yarn
# VSCode 直接打开当前项目
# code .

用最新的VSCode 打开项目,找到 package.jsonscripts 属性中的 test 命令。鼠标停留在test命令上,会出现 运行命令调试命令 的选项,选择 调试命令 即可。

调试如图所示:

a33ae89f11eff8866b1b246d85f7783e.png
调试如图所示

VSCode 调试 Node.js 说明如下图所示:

ffe8b69c592d0438388740f021da55d5.png
VSCode 调试 Node.js 说明

更多调试详细信息,可以查看这篇文章新手向:前端程序员必学基本技能——调试JS代码。

跟着调试,我们来看主文件。

3.3 主文件仅有22行源码

// index.js
import {promisify} from 'node:util';
import childProcess from 'node:child_process';const execFile = promisify(childProcess.execFile);export default async function remoteGitTags(repoUrl) {const {stdout} = await execFile('git', ['ls-remote', '--tags', repoUrl]);const tags = new Map();for (const line of stdout.trim().split('\n')) {const [hash, tagReference] = line.split('\t');// Strip off the indicator of dereferenced tags so we can override the// previous entry which points at the tag hash and not the commit hash// `refs/tags/v9.6.0^{}` → `v9.6.0`const tagName = tagReference.replace(/^refs\/tags\//, '').replace(/\^{}$/, '');tags.set(tagName, hash);}return tags;
}

源码其实一眼看下来就很容易懂。

3.4 git ls-remote --tags

支持远程仓库链接。

git ls-remote 文档[4]

如下图所示:

048a524389d46f2e9841f88eb4fc66e0.png
ls-remote

获取所有tags git ls-remote --tags https://github.com/vuejs/vue-next.git

把所有 tags 和对应的 hash值 存在 Map 对象中。

3.5 node:util

Node 文档[5]

Core modules can also be identified using the node: prefix, in which case it bypasses the require cache. For instance, require('node:http') will always return the built in HTTP module, even if there is require.cache entry by that name.

也就是说引用 node 原生库可以加 node: 前缀,比如 import util from 'node:util'

看到这,其实原理就明白了。毕竟只有22行代码。接着讲述 promisify

4. promisify

源码中有一段:

const execFile = promisify(childProcess.execFile);

promisify 可能有的读者不是很了解。

接下来重点讲述下这个函数的实现。

promisify函数是把 callback 形式转成 promise 形式。

我们知道 Node.js 天生异步,错误回调的形式书写代码。回调函数的第一个参数是错误信息。也就是错误优先。

我们换个简单的场景来看。

4.1 简单实现

假设我们有个用JS加载图片的需求。我们从 这个网站[6] 找来图片。

examples
const imageSrc = 'https://www.themealdb.com/images/ingredients/Lime.png';function loadImage(src, callback) {const image = document.createElement('img');image.src = src;image.alt = '公众号若川视野专用图?';image.style = 'width: 200px;height: 200px';image.onload = () => callback(null, image);image.onerror = () => callback(new Error('加载失败'));document.body.append(image);
}

我们很容易写出上面的代码,也很容易写出回调函数的代码。需求搞定。

loadImage(imageSrc, function(err, content){if(err){console.log(err);return;}console.log(content);
});

但是回调函数有回调地狱等问题,我们接着用 promise 来优化下。

4.2 promise 初步优化

我们也很容易写出如下代码实现。

const loadImagePromise = function(src){return new Promise(function(resolve, reject){loadImage(src, function (err, image) {if(err){reject(err);return;}resolve(image);});});
};
loadImagePromise(imageSrc).then(res => {console.log(res);
})
.catch(err => {console.log(err);
});

但这个不通用。我们需要封装一个比较通用的 promisify 函数。

4.3 通用 promisify 函数

function promisify(original){function fn(...args){return new Promise((resolve, reject) => {args.push((err, ...values) => {if(err){return reject(err);}resolve(values);});// original.apply(this, args);Reflect.apply(original, this, args);});}return fn;
}const loadImagePromise = promisify(loadImage);
async function load(){try{const res = await loadImagePromise(imageSrc);console.log(res);}catch(err){console.log(err);}
}
load();

需求搞定。这时就比较通用了。

这些例子在我的仓库存放在 examples 文件夹中。可以克隆下来,npx http-server .跑服务,运行试试。

2d6b19a03d1b3858d2b49ac8696471a1.png
examples

跑失败的结果可以把 imageSrc 改成不存在的图片即可。

promisify 可以说是面试高频考点。很多面试官喜欢考此题。

接着我们来看 Node.js 源码中 promisify 的实现。

4.4 Node utils promisify 源码

github1s node utils 源码[7]

源码就暂时不做过多解释,可以查阅文档。结合前面的例子,其实也容易理解。

utils promisify 文档[8]

const kCustomPromisifiedSymbol = SymbolFor('nodejs.util.promisify.custom');
const kCustomPromisifyArgsSymbol = Symbol('customPromisifyArgs');let validateFunction;function promisify(original) {// Lazy-load to avoid a circular dependency.if (validateFunction === undefined)({ validateFunction } = require('internal/validators'));validateFunction(original, 'original');if (original[kCustomPromisifiedSymbol]) {const fn = original[kCustomPromisifiedSymbol];validateFunction(fn, 'util.promisify.custom');return ObjectDefineProperty(fn, kCustomPromisifiedSymbol, {value: fn, enumerable: false, writable: false, configurable: true});}// Names to create an object from in case the callback receives multiple// arguments, e.g. ['bytesRead', 'buffer'] for fs.read.const argumentNames = original[kCustomPromisifyArgsSymbol];function fn(...args) {return new Promise((resolve, reject) => {ArrayPrototypePush(args, (err, ...values) => {if (err) {return reject(err);}if (argumentNames !== undefined && values.length > 1) {const obj = {};for (let i = 0; i < argumentNames.length; i++)obj[argumentNames[i]] = values[i];resolve(obj);} else {resolve(values[0]);}});ReflectApply(original, this, args);});}ObjectSetPrototypeOf(fn, ObjectGetPrototypeOf(original));ObjectDefineProperty(fn, kCustomPromisifiedSymbol, {value: fn, enumerable: false, writable: false, configurable: true});return ObjectDefineProperties(fn,ObjectGetOwnPropertyDescriptors(original));
}promisify.custom = kCustomPromisifiedSymbol;

5. ES6+ 等知识

文中涉及到了Mapfor of、正则、解构赋值。

还有涉及封装的 ReflectApplyObjectSetPrototypeOfObjectDefinePropertyObjectGetOwnPropertyDescriptors 等函数都是基础知识。

这些知识可以查看esma规范[9],或者阮一峰老师的《ES6 入门教程》[10] 等书籍。

6. 总结

一句话简述 remote-git-tags 原理:使用Node.js的子进程 child_process 模块的execFile方法执行 git ls-remote --tags repoUrl 获取所有 tagstags 对应 hash 值 存放在 Map 对象中。

文中讲述了我们可以循序渐进,借助调试、理清主线、查阅资料、总结记录的流程看源码。

通过 remote-git-tags 这个22行代码的仓库,学会了 Node 加载采用什么模块,知道了原来 git ls-remote --tags支持远程仓库,学到了面试高频考点 promisify 函数原理和源码实现,巩固了一些 ES6+ 等基础知识。

建议读者克隆我的仓库[11]动手实践调试源码学习。

后续也可以看看 es6-promisify[12] 这个库的实现。

最后可以持续关注我@若川。欢迎加我微信 ruochuan12 交流,参与 源码共读 活动,大家一起学习源码,共同进步。

参考资料

[1]

本文仓库 remote-git-tags-analysis,求个star^_^: https://github.com/lxchuan12/remote-git-tags-analysis.git

[2]

npm 包描述信息: https://npm.im/remote-git-tags

[3]

npm-check-updates: https://www.npmjs.com/package/npm-check-updates

[4]

git ls-remote 文档: https://git-scm.com/docs/git-ls-remote

[5]

Node 文档: https://nodejs.org/dist/latest-v16.x/docs/api/modules.html

[6]

这个网站: https://www.themealdb.com/api.php

[7]

github1s node utils 源码: https://github1s.com/nodejs/node/blob/master/lib/internal/util.js#L343

[8]

utils promisify 文档: http://nodejs.cn/api/util/util_promisify_original.html

[9]

esma规范: https://yanhaijing.com/es5/

[10]

《ES6 入门教程》: https://es6.ruanyifeng.com/

[11]

我的仓库: https://github.com/lxchuan12/remote-git-tags-analysis.git

[12]

es6-promisify: https://github.com/mikehall314/es6-promisify

最近组建了一个江西人的前端交流群,如果你是江西人可以加我微信 ruochuan12 私信 江西 拉你进群。

推荐阅读

1个月,200+人,一起读了4周源码
我历时3年才写了10余篇源码文章,但收获了100w+阅读

老姚浅谈:怎么学JavaScript?

我在阿里招前端,该怎么帮你(可进面试群)

a83325eae61f0693f903ea78a713ff5d.gif

················· 若川简介 ·················

你好,我是若川,毕业于江西高校。现在是一名前端开发“工程师”。写有《学习源码整体架构系列》10余篇,在知乎、掘金收获超百万阅读。
从2014年起,每年都会写一篇年度总结,已经写了7篇,点击查看年度总结。
同时,最近组织了源码共读活动,帮助1000+前端人学会看源码。公众号愿景:帮助5年内前端人走向前列。

6cf43d70cc457039869c5d44ad1ae612.png

识别方二维码加我微信、拉你进源码共读

今日话题

略。欢迎分享、收藏、点赞、在看我的公众号文章~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/275225.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java中filter的用法

filter过滤器主要使用于前台向后台传递数据是的过滤操作。程度很简单就不说明了&#xff0c;直接给几个已经写好的代码&#xff1a; 一、使浏览器不缓存页面的过滤器 Java代码 import javax.servlet.*;import javax.servlet.http.HttpServletResponse;import java.io.IOExcept…

open-falcon_NASA在Falcon 9上带回了蠕虫-其背后的故事是什么?

open-falconYes, that’s right. The classic NASA “worm” logo is back! An image of the revived NASA worm logo was released on Twitter by NASA Administrator Jim Bridenstine as well as press release on the NASA.gov website. NASA explained that original NASA …

听说你对 ES6 class 类还不是很了解

大家好&#xff0c;我是若川。最近组织了源码共读活动&#xff0c;感兴趣的可以加我微信 ruochuan12 参与。前言在ES5中是原型函数&#xff0c;到了ES6中出现了"类"的概念。等同于是ES5的语法糖&#xff0c;大大提升了编写代码的速度&#xff0c;本文只讲一些常用的&…

一篇文章带你搞懂前端面试技巧及进阶路线

大家好&#xff0c;我是若川。最近有很多朋友给我后台留言&#xff1a;自己投了不少简历&#xff0c;但是收到的面试邀请却特别少&#xff1b;好不容易收到了大厂的面试邀请&#xff0c;但由于对面试流程不清楚&#xff0c;准备的特别不充分&#xff0c;结果也挂了&#xff1b;…

小屏幕 ui设计_UI设计基础:屏幕

小屏幕 ui设计重点 (Top highlight)第4部分 (Part 4) Welcome to the fourth part of the UI Design basics. This time we’ll cover the screens you’ll likely design for. This is also a part of the free chapters from Designing User Interfaces.欢迎使用UI设计基础知…

RabbitMQ指南之四:路由(Routing)和直连交换机(Direct Exchange)

在上一章中&#xff0c;我们构建了一个简单的日志系统&#xff0c;我们可以把消息广播给很多的消费者。在本章中我们将增加一个特性&#xff1a;我们可以订阅这些信息中的一些信息。例如&#xff0c;我们希望只将error级别的错误存储到硬盘中&#xff0c;同时可以将所有级别&am…

不用任何插件实现 WordPress 的彩色标签云

侧边栏的标签云&#xff08;Tag Cloud&#xff09;一直是 WordPress 2.3 以后的内置功能&#xff0c;一般直接调用函数wp_tag_cloud 或者在 Widgets 里开启即可&#xff0c;但是默认的全部是一个颜色&#xff0c;只是大小不一样&#xff0c;很是不顺眼&#xff0c;虽然可以用 S…

随时随地能写代码, vscode.dev 出手了

大家好&#xff0c;我是若川。最近组织了源码共读活动&#xff0c;感兴趣的可以加我微信 ruochuan12 参与。今天偶然看到了 VSCode 官方发布了一条激动人心的 Twitter&#xff0c;vscode.dev[1] 域名上线了&#xff01;image-20211021211915942新的域名 vscode.dev[2] 它是一个…

七种主流设计风格_您是哪种设计风格?

七种主流设计风格重点 (Top highlight)I had an idea for another mindblowing test, so here it is. Since you guys liked the first one so much, and I got so many nice, funny responses and private messages on how accurate it actually was, I thought you will prob…

React 18 Beta 来了

大家好&#xff0c;我是若川。最近组织了源码共读活动&#xff0c;感兴趣的可以加我微信 ruochuan12 参与&#xff0c;目前近3000人参与。经过「React18工作组」几个月工作&#xff0c;11月16日v18终于从Alpha版本更新到Beta版本。本文会解释&#xff1a;这次更新带来的变化对开…

osg着色语言着色_探索数字着色

osg着色语言着色Learn how to colorize icons with your NounPro subscription and Adobe Illustrator.了解如何使用NounPro订阅和Adobe Illustrator为图标着色。 For those who want to level up their black and white Noun Project icons with a splash of color, unlockin…

CSS3实践之路(一):CSS3之我观

CSS 的英文全称Cascading Style Sheets&#xff0c;中文意思是级联样式表,通过设立样式表&#xff0c;可以统一地控制HMTL中各DOM元素的显示属性。级联样式表可以使人更能有效地控制网页外观。使用级联样式表&#xff0c;可以扩充精确指定网页元素位置&#xff0c;外观以及创建…

18个项目必备的JavaScript代码片段——数组篇

大家好&#xff0c;我是若川。最近组织了源码共读活动&#xff0c;感兴趣的可以加我微信 ruochuan12 参与&#xff0c;目前近3000人参与&#xff0c;0-5年工作经验的都可以参与学习。1.chunk转换二维数组将数组&#xff08;array&#xff09;拆分成多个数组&#xff0c;并将这些…

美学评价_卡美学的真正美

美学评价In collectible card games like Hearthstone, Legends of Runeterra, and Magic: The Gathering, the aesthetic of the cards is indubitably one of the greatest highlights for many, if not all players. Although the game loop is reliant on physically build…

好程序员web前端分享CSS Bug、CSS Hack和Filter学习笔记

为什么80%的码农都做不了架构师&#xff1f;>>> CSS Bug、CSS Hack和Filter学习笔记 1)CSS Bug:CSS样式在各浏览器中解析不一致的情况&#xff0c;或者说CSS样式在浏览器中不能正确显示的问题称为CSS bug. 2)CSS Hack: CSS中&#xff0c;Hack是指一种兼容CSS在不同…

ux和ui_设计更好的结帐体验-UX / UI案例研究

ux和uiPlated Cuisine is a food ordering and delivery app for Plated Cuisine Restaurant founded and managed by Rayo Odusanya.Plated Cuisine是由Rayo Odusanya创建和管理的Plated Cuisine Restaurant的食品订购和交付应用程序。 A short background about Rayo Rayo O…

Django中ajax发送post请求,报403错误CSRF验证失败解决办法

今天学习Django框架&#xff0c;用ajax向后台发送post请求&#xff0c;直接报了403错误&#xff0c;说CSRF验证失败&#xff1b;先前用模板的话都是在里面加一个 {% csrf_token %} 就直接搞定了CSRF的问题了&#xff1b;很显然&#xff0c;用ajax发送post请求这样就白搭了&…

如何在EXCEL中添加下拉框

筛选主要是将已有列的信息以下拉框的形式显示出来 选中数据栏中的筛选按钮即可生成 如果是想添加未有信息则如下图步骤 首先&#xff0c;选择你要出现下拉的区域&#xff0c;在数据栏中的选择数据有效性 然后&#xff0c;下面对话框中&#xff0c;有效性条件中按如下设置即可&a…

每次新增页面复制粘贴?100多行源码的 element-ui 的新增组件功能教你解愁

1. 前言大家好&#xff0c;我是若川。最近组织了源码共读活动&#xff0c;感兴趣的可以点此加我微信ruochuan12 参与&#xff0c;每周大家一起学习200行左右的源码&#xff0c;共同进步。已进行三个月了&#xff0c;很多小伙伴表示收获颇丰。想学源码&#xff0c;极力推荐之前我…

原子设计_您需要了解的有关原子设计的4件事

原子设计重点 (Top highlight)Industries such as Architecture or Industrial Design have developed smart modular systems for manufacturing extremely complex objects like airplanes, ships, and skyscrapers. Inspired by this, Atomic Design was proposed as a syst…