本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。
学习目标
-
- Node 加载采用什么模块
-
- 获取 git 仓库所有 tags 的原理
-
- 学会调试看源码
-
- 学会面试高频考点 promisify 的原理和实现
环境准备
git clone https://github.com/lxchuan12/remote-git-tags-analysis.git
克隆相关的代码,找到remote-git-tags文件目录,执行:
yarn 或 npm install
测试代码
我们可以根据已有的代码找到 test.js 文件,可以对remoteGitTags方法进行测试。
remoteGitTags 源码
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;
}
promisify
函数是把 callback
形式转成 promise
形式.
promisify 函数
准备一个异步请求图片的方法
const imageSrc = 'https://www.themealdb.com/images/ingredients/Lime.png';
const loadImage = (src, callback)=>{
const image = document.createElement('img');
image.src = src;
image.alt = '测试';
image.style = 'width: 200px;height: 200px';
image.onload = () => callback(null, image);
image.onerror = () => callback(new Error('Loading Error!'));
document.body.append(image);
}
编写一个基础版本的CallBack调用方式
const cb = (err,imageData) =>{
if(err){
console.error(err);
return;
}
console.log(imageData)
}
loadImage(imageSrc, cb);
这个基础的CallBack调用方式可以实现图片的加载,但是使用CallBack的方式难免会因为回调函数过多造成回调地狱。
Promise 初步优化
const loadImagePromise = function(src){
return new Promise(function(resolve, reject){
loadImage(src, function (err, image) {
if(err){
reject(err);
return;
}
resolve(image);
});
});
};
loadImagePromise(imageSrc).then(res => {
console.log(res);
})
.catch(err => {
console.log(err);
});
弊端很明显,我们不能决定调用哪一个函数,所以出现了优化版本。
Promise 进一步优化
function promisify(originalFun){
function fn(...args){
return new Promise((resolve, reject) => {
args.push((err, ...values) => {
if(err){
return reject(err);
}
resolve(values);
});
console.log(args)
originalFun.apply(this, args);
// Reflect.apply(originalFun, this, args);
});
}
return fn;
}
const loadImagePromise = promisify(loadImage);
async function load(){
try{
const res = await loadImagePromise(imageSrc);
console.log(res);
}
catch(err){
console.log(err);
}
}
load();
Node utils 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;
总结
可能有些地方不是很清楚,但是大概知道promisify的实现原理,从学习中也看到了自己基础知识的不足。自己有空的时候会恶补基础知识。