promisify 源码学习

67 阅读3分钟

前言

获取远程仓库tags

作用及应用场景

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

原理:通过执行 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。

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 获取所有 tagstags 对应 hash 值 存放在 Map 对象中。
  • promisify实现原理: 自己实现一个返回promise的函数,使用元编程把原函数的行为代理到该函数上完成函数的异步转化。