开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第6天,点击查看活动详情
本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。
【若川视野 x 源码共读】第14期 | promisify 点击了解本期详情一起参与。
今天阅读的是:remote-git-tags
尝试使用
import remoteGitTags from 'remote-git-tags';
console.log(await remoteGitTags('https://github.com/sindresorhus/remote-git-tags'));
//=> Map {'v1.0.0' => '69e308412e2a5cffa692951f0274091ef23e0e32', …}
- 这个库的作用就是获取远程端的所有标签
源码分析
package.json
- 我们可以看到入口文件
index.js
测试用例
- 测试用例也非常简单
import test from 'ava';
import remoteGitTags from './index.js';
test('main', async t => {
const tags = await remoteGitTags('https://github.com/sindresorhus/got');
t.is(tags.get('v6.0.0'), 'e5c2d9e93137263c68db985b3dc5b57865c67b82');
t.is(tags.get('v5.0.0'), '0933d0bb13f704bc9aabcc1eec7a8e33dc8aba51');
});
主文件
import { promisify } from 'node:util';
import childProcess from 'node:child_process';
const execFile = promisify(childProcess.execFile);
export default async function remoteGitTags(repoUrl) {
// 执行 git ls-remote --tags
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');
// 返回结果 举个例子:
// d6602ec5194c87b0fc87103ca4d67251c76f233a refs/tags/v0.99
// f25a265a342aed6041ab0cc484224d9ca54b6f41 refs/tags/v0.99.1
// 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;
}
代码非常简短,我们可以容易的知道怎么实现的。在代码中有一个值得注意的点,执行命令行的execFile中有一个promisify
那么,这个promisify的作用是啥,具体是怎么实现的呢
-
promisify是将需要传入回调函数的方法转化成promise的形式,解决了需要传入回调函数导致回调地狱的问题
promisify
我们直接看promisify可能有点难理解,我们从一个例子开始
读取当前文件夹的内容
我们可以写出以下代码:
// index.ts
import { readdir } from 'fs'
readdir('.', 'utf-8', (err, files) => {
if (err) throw err
console.log(files)
})
我们将回调函数改为promise的形式
promise解决了回调地狱的问题
const readdirPromise = (fileSrc) => {
return new Promise((resolve, reject) => {
readdir(fileSrc, (err, files) => {
if (err) {
reject(err)
return
}
resolve(files)
})
})
}
readdirPromise('.').then((res) => {
console.log(res)
})
readdirPromise('.').catch((err) => {
console.log(err)
})
上述写法能基本满足需求,我们对其进行进一步抽离,使得这个方法更具通用性
import { readdir } from 'fs'
const readFile = (filePath) =>
readdir(filePath, 'utf-8', (err, files) => {
if (err) throw err
console.log(files)
})
const promisify = (original) => {
return function fn(...args) {
return new Promise((resolve, reject) => {
args.push((err, ...values) => {
if (err) {
return reject(err)
}
resolve(values)
})
// 赋值操作
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/set
Reflect.apply(original, this, args)
})
}
}
const readFilePromise = promisify(readFile)
async function readFilePromisify() {
try {
const res = await readFilePromise('.')
console.log(res)
} catch (err) {
console.log(err)
}
}
readFilePromisify()
源码
接下来,我们来看看源码中是怎么实现的
module.exports = function promisify(orig) {
// 校验orig参数合法性
if (typeof orig !== 'function') {
// 省略...
}
// 校验promisify.custom 合法性
if (hasSymbols && orig[kCustomPromisifiedSymbol]) {
// 省略...
}
// Names to create an object from in case the callback receives multiple
// arguments, e.g. ['stdout', 'stderr'] for child_process.exec.
var argumentNames = orig[kCustomPromisifyArgsSymbol]
var promisified = function fn() {
var args = $slice(arguments)
var self = this // eslint-disable-line no-invalid-this
return new Promise(function (resolve, reject) {
orig.apply(
self,
$concat(args, function (err) {
var values = arguments.length > 1 ? $slice(arguments, 1) : []
if (err) {
reject(err)
} else if (
typeof argumentNames !== 'undefined' &&
values.length > 1
) {
var obj = {}
$forEach(argumentNames, function (name, index) {
obj[name] = values[index]
})
resolve(obj)
} else {
resolve(values[0])
}
})
)
})
}
promisified.__proto__ = orig.__proto__ // eslint-disable-line no-proto
Object.defineProperty(promisified, kCustomPromisifiedSymbol, {
configurable: true,
enumerable: false,
value: promisified,
writable: false
})
var descriptors = getOwnPropertyDescriptors(orig)
forEach(descriptors, function (k, v) {
try {
Object.defineProperty(promisified, k, v)
} catch (e) {
// handle nonconfigurable function properties
}
})
return promisified
}
总结
- 通过对
remote-git-tags的分析,我们知道其执行了命令git ls-remote --tags来获取远程仓库的tag,通过正则截取了当前的版本号。 - 通过对源码的分析,我们知道有一个
promisify的函数实现原理。该方法传入需要回调函数作为参数的方法,转化为Promise的形式处理,在其内部通过Reflect.apply来传递参数。