1 学习目标
了解 remote-git-tags 作用和使用场景 了解 promisify 实现原理
2 库介绍
这个库的作用是:从远程仓库获取所有标签。 原理:通过执行 gti ls-remote --tags xxx(仓库路径) 获取 tags 应用:可以用于切换 tags 发布版本等
// 如何使用
import test from 'ava';
import remoteGitTags from './index.js';
// 第一个参数是测试用例的名称
// 第二个参数是一个函数,该函数会注入t对象,所有的断言都是通过这个注入的t进行的
test('main', async () => {
const tags = await remoteGitTags('https://github.com/sindresorhus/got');
console.log(tags);
});
/*
Map(149) {
'v0.1.0' => '1e7746d8c44a75ee9ec8ed94356dd5e8ba9c3bc2',
'v0.1.1' => '98d1263b1331573dbc6c3110addb038dc5804bb8',
.....
}
*/
3 拉取源码
克隆源码文件 git clone github.com/sindresorhu… 进入文件 cd remote-git-tags
4 package.json文件
Node 12 以下都是 CommonJS 模块机制。 Node 13 增加了对 ES6 模块的支持
默认情况下
- 对于.js 结尾文件和无拓展名文件是以 CommonJS 方式加载
- 对于.mjs 文件则当作 es 模块加载
如果 package.json 文件中存在 type 属性- 值为 moduel 则当作 es 模块处理
- 值为 commonjs 则被当作 commonJs 模块处理
// ava 一个开源的Node.js测试运行器
// xo eslint包装器,内置了eslint
{
"type": "module",
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"scripts": {
"test": "xo && ava"
}
}
5 源码文件
源码只有22行,可以很明显的看出来
获取所有 tags git ls-remote --tags github.com/sindresorhu…
把所有 tags 和对应的 hash值 存在 Map 对象中
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;
}
6 调试源码
进行调试
VSCode 打开当前项目,打开 package.json 文件,执行 script 属性中的 test 命令并执行调试脚本命令
7 promisify
Node.js 提供了一个util 模块,提供了一些方法
1、util.promisify
promisify 方法和 callbackify 方法功能正好相反,将 callback 转换成 Promise
2、util.format
第一个参数为一个字符串,用于指定格式,后面的参数为要格式化的参数,返回指定格式的字符串
3、util.callbackify
将一个 async 函数,或者返回值为 Promise 的函数,转换 callback
4、util.inspect
将任意对象转换为字符串。
7.1 简单实现
在以前经常会遇到回调地狱的问题,一层套一层的callback,如何实现一个promise?
// promisify 参数 arg 为函数
function promisify(arg){
return (...args) => { //返回第二层函数,用来接收参数
return new Promise((reslove,reject) => { //返回promise
arg(...args,(err,data) => {
if (err) {
reject(err)
}else {
reslove(data); //返回数据
}
})
})
}
}
// 例如
const fs = require('fs');
// 使用 promise 将 fs.readdir 转换成 promise
const readdir = promisify(fs.readdir);
// 使用 promise 再由 async/await 转成同步
(async ()=>{
const read = await readdir('./');
console.log(read); //打印数据
})()
// 或者也可以直接使用 then
readdir('./').then(res => {
// do something...
}).catch(err =>{
// 捕获错误信息
})
7.2 util.promisify源码 ( 源码地址 )
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;
8 child_process
Node 的子进程模块
// 子进程 包括以下模块
1、ChildProcess
ChildProcess 的实例,表示衍生的子进程,通过child_process.spawn() 等方法来创建出来的
2、exec
衍生 shell 并在该 shell 中运行命令,完成后将 stdout 和 stderr 传给回调函数
3、execFile
与exec类似,不同之处在于,默认情况下,它直接衍生命令,而不先衍生 shell
4、execFileSync
exec 的同步版本,其将阻塞 Node.js 事件循环
5、execSync
execFile 的同步版本,其将阻塞 Node.js 事件循环
6、fork
衍生新的 Node.js 进程并使用建立的 IPC 通信通道(其允许在父子进程之间发送消息)调用指定的模块
7、spawn
异步衍生子进程,不会阻塞 Node.js 事件循环
8、spawnSync: [Function: spawnSync]
同步衍生子进程,会阻塞事件循环,直到衍生的进程退出或终止
9 总结
通过本文,一句话简述 remote-git-tags 原理
通过 Node.js 的子进程 childProcess 中的 execFile 模块,执行 git ls-remote --tags repoUrl 获取所有 tags 和 tags 对应 hash 值存放在 Map 对象中
学习了 Node 模块使用什么加载方式,学习了 promisify 的原理,了解到 node 的 xo 库和 ava 库