前言
- 本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。
- 这是源码共读的第14期,链接:第14期 | promisify
获取远程仓库tags
- 源码地址:remote-git-tags
- 测试仓库地址:remote-git-tags-analysis
作用及应用场景
这个库的作用是:从远程仓库获取所有标签。
原理:通过执行 git ls-remote --tags repoUrl (仓库路径)获取 tags
应用场景:可以看有哪些包依赖的这个包。 npm 包描述信息
其中一个比较熟悉的是 npm-check-updates
npm-check-updates 将您的 package.json 依赖项升级到最新版本,忽略指定的版本。
还有场景可能是 github 中获取所有 tags 信息,切换 tags 或者选定 tags 发布版本等,比如微信小程序版本。
源码分析
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;
}
源码不多,就是用node.child_process.execFile执行git ls-remote --tags repoUrl,然后处理得到的结果并返回一个映射表。
promisify
- promisify 源码地址
node.util.promisify是在node.js 8.x版本中新增的一个工具,用于将老式的Error first callback转换为Promise对象
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;
整理下具体实现就是
-
用
Symbol.for(key)先判断是否有底层实现,有就直接使用并返回一个元编程包裹的底层实现 -
没有底层实现就自己封装,封装思路如下:
- 实现一个fn,该fn返回一个
promise,该promise负责负责收集原函数 - 把入参的函数的属性挂在到fn上
- 把当前实现设置成底层实现,就是把Symbol('util.promisify.custom')映射为fn
Object.defineProperties()方法直接在fn上定义新的属性为入参的函数的属性,并返回fn。
- 实现一个fn,该fn返回一个
Symbol.for(key)
方法会根据给定的键 key,来从运行时的 symbol 注册表中找到对应的 symbol,如果找到了,则返回它,否则,新建一个与该键关联的 symbol,并放入全局 symbol 注册表中。
Reflect.apply()
Reflect .apply() 通过指定的参数列表发起对目标(target)函数的调用。
总结
remote-git-tags实现原理:使用Node.js的子进程child_process模块的execFile方法执行git ls-remote --tags repoUrl获取所有tags和tags对应hash值 存放在Map对象中。promisify实现原理: 自己实现一个返回promise的函数,使用元编程把原函数的行为代理到该函数上完成函数的异步转化。