- 本文参加了由公众号@若川视野 发起的每周源码共读活动, 点击了解详情一起参与。
- 这是源码共读的第14期,链接:www.yuque.com/ruochuan12/…。
本次源码阅读分析的是第14期的内容, 主要通过 remote-git-tags 来学习 node 中的 promisify
首先简单介绍下 remote-git-tags, 这个库的作用是:从远程仓库获取所有标签。
通过执行 git ls-remote --tags repoUrl (仓库路径)获取 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 模块 nodejs.org/dist/latest…
import {promisify} from 'node:util'; import childProcess from 'node:child_process' -
package.json 文件中
"type": "module"的作用 -
promisify采用遵循常见的错误优先的回调风格的函数(也就是将(err, value) => ...回调作为最后一个参数),并返回一个返回 promise 的版本。
下面着重分析一下 promisify
//1. SymbolFor / ObjectDefineProperty 等函数, 实际上就是将 js 的内置函数进行保存, 以防止用户修改了内置函数.
const kCustomPromisifiedSymbol = SymbolFor('nodejs.util.promisify.custom'); // 使用symbol.for 保证获取的是同一个 symbol 值
const kCustomPromisifyArgsSymbol = Symbol('customPromisifyArgs');
//2. validateFunction 验证 original 是不是函数
let validateFunction;
function promisify(original) {
// Lazy-load to avoid a circular dependency.
if (validateFunction === undefined)
({ validateFunction } = require('internal/validators'));
validateFunction(original, 'original');
//3. 这里的判断是否有自定义的 promise 化函数, 如果有直接返回 参见 http://nodejs.cn/api/util.html#custom-promisified-functions
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.
//4. 如果回调函数有多个值, 则可以通过自定义参数来获取一个保存了这些值的对象.
const argumentNames = original[kCustomPromisifyArgsSymbol];
// 5. 关键函数 fn, 返回了一个 promise 对象, 提供了一个通用的用于处理 *错误优先的回调风格的函数* 的回调函数(被!!!包裹的部分)
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]);
}
});
// 执行 original 函数, 绑定 this, 输入 args
ReflectApply(original, this, args);
}!!!);
}
// 将 fn 的原型设置为 original 的原型
ObjectSetPrototypeOf(fn, ObjectGetPrototypeOf(original));
// fn 上挂载一个 symbol 属性,指向 fn
ObjectDefineProperty(fn, kCustomPromisifiedSymbol, {
value: fn, enumerable: false, writable: false, configurable: true
});
// 将 original 上其它属性复制到 fn 上, 并返回
return ObjectDefineProperties(
fn,
ObjectGetOwnPropertyDescriptors(original)
);
}
// 6. 将 kCustomPromisifiedSymbol 挂载到 promisify 的 custom 属性上
promisify.custom = kCustomPromisifiedSymbol;
-
SymbolForObjectDefineProperty等函数这些函数是在
\lib\internal\per_context\primordials.js中定义的, 实际上就是将 js 的内置函数进行保存, 以防止用户修改了内置函数. -
validateFunction验证original是不是函数const validateFunction = hideStackFrames((value, name) => { if (typeof value !== 'function') throw new ERR_INVALID_ARG_TYPE(name, 'Function', value); }); -
这里的判断是否有自定义的 promise 化函数, 如果有直接返回, 替代了下面的
fn函数, 这样有利于非错误优先的回调作为最后一个参数的标准格式, 详细可以参见 nodejs.cn/api/util.ht… -
如果回调函数有多个值, 则可以通过自定义参数来获取一个保存了这些值的对象.
{ const firstValue = 5; const secondValue = 17; function fn(callback) { callback(null, firstValue, secondValue); } fn[customPromisifyArgs] = ['first', 'second']; promisify(fn)().then(common.mustCall((obj) => { assert.deepStrictEqual(obj, { first: firstValue, second: secondValue }); })); } -
将 kCustomPromisifiedSymbol 挂载到 promisify 的 custom 属性上, 这样用户即可自定义 promisify 函数
doSomething[util.promisify.custom] = (foo) => { return getPromiseSomehow(); };
上述就是对 promisify 的源码解读, 阅读后还有一些地方不甚了解, 希望得到大家的帮助解答.
问题:
- 这些代码我理解是将 original 的东西都复制给 fn, 这样做是为了解决什么问题呢?
...
ObjectSetPrototypeOf(fn, ObjectGetPrototypeOf(original));
...
return ObjectDefineProperties(
fn,
ObjectGetOwnPropertyDescriptors(original)
);
-
在 fn 上还设置这样的一个属性有什么用呢?
ObjectDefineProperty(fn, kCustomPromisifiedSymbol, { value: fn, enumerable: false, writable: false, configurable: true });
TODO:
- 梳理模块化知识
- 复习 promise
- ts namespace