学习:callback promisify 化的 Node.js 源码实现

215 阅读3分钟

学习川哥整理的源码文章,写下的读书笔记。 juejin.cn/post/702873…

一、学习目标:

  1. 学会用node调试代码
  2. promisify实现原理
  3. ES6的相关知识

二、源码学习:

1、首先克隆源码:

git clone <https://github.com/lxchuan12/remote-git-tags-analysis.git>
  • 知识储备:

(a)如果是 .mjs 结尾的文件,则 Node 始终会将它作为 ES6 模块来加载。 如果是 .cjs 结尾的文件,则 Node 始终会将它作为 CommonJS 模块来加载。

(b)对于以 .js 结尾的文件,默认是 CommonJS 模块。如果同级目录及所有目录有 package.json 文件,且 type 属性为module 则使用 ES6 模块。type 值为 commonjs 或者为空或者没有 package.json 文件,都是默认 commonjs 模块加载。

2、学会调试源码:

(1)用最新的VSCode 打开项目,找到 package.json 的 scripts 属性中的 test 命令。鼠标停留在test命令上,会出现 运行命令 和 调试命令 的选项,选择 调试命令 即可。

(2)用cmd命令行执行调试的命令,这里是使用 npm test。

下面这个图片非常清晰的展示操作过程:

image.png

但是有一点要非常注意:在克隆项目和保存文件的过程中,养成不要使用中文的习惯,不然可能会导致一些意想不到的问题,下面是我出现的情况:

ed988bc1fa08ac2621e9e3a78eb8246.jpg

3、源码:

// index.js
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的实现原理:

1、初步优化实现:

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);
});

2、通用 promisify 函数:

function promisify(original){
    function fn(...args){
        return new Promise((resolve, reject) => {
            args.push((err, ...values) => {
                if(err){
                    return reject(err);
                }
                resolve(values);
            });
            // original.apply(this, args);
            Reflect.apply(original, 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();

3、事实上NodeJS自己的util库里就已经实现了promisify

const {promisify} = require('util');
let readFilePromisify = promisify(fs.readFile);

四、Reflect相关知识:

Reflect对象与Proxy对象一样,也是 ES6 为了操作对象而提供的新 API。Reflect对象的设计目的有这样几个。

1、 将Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上。现阶段,某些方法同时在ObjectReflect对象上部署,未来的新方法将只部署在Reflect对象上。也就是说,从Reflect对象上可以拿到语言内部的方法。

2、 修改某些Object方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回false

if (Reflect.defineProperty(target, property, attributes)) {
  // success
} else {
  // failure
}

3、 让Object操作都变成函数行为。某些Object操作是命令式,比如name in objdelete obj[name],而Reflect.has(obj, name)Reflect.deleteProperty(obj, name)让它们变成了函数行为。

Reflect.has(Object, 'assign') 

4、Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。这就让Proxy对象可以方便地调用对应的Reflect方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy怎么修改默认行为,你总可以在Reflect上获取默认行为。

Proxy(target, {
  set: function(target, name, value, receiver) {
    var success = Reflect.set(target, name, value, receiver);
    if (success) {
      console.log('property ' + name + ' on ' + target + ' set to ' + value);
    }
    return success;
  }
});

上面代码中,Proxy方法拦截target对象的属性赋值行为。它采用Reflect.set方法将值赋值给对象的属性,确保完成原有的行为,然后再部署额外的功能。

5、Reflect对象一共有 13 个静态方法。

  • Reflect.apply(target, thisArg, args)
  • Reflect.construct(target, args)
  • Reflect.get(target, name, receiver)
  • Reflect.set(target, name, value, receiver)
  • Reflect.defineProperty(target, name, desc)
  • Reflect.deleteProperty(target, name)
  • Reflect.has(target, name)
  • Reflect.ownKeys(target)
  • Reflect.isExtensible(target)
  • Reflect.preventExtensions(target)
  • Reflect.getOwnPropertyDescriptor(target, name)
  • Reflect.getPrototypeOf(target)
  • Reflect.setPrototypeOf(target, prototype)

上面这些方法的作用,大部分与Object对象的同名方法的作用都是相同的,而且它与Proxy对象的方法是一一对应的。 可点击了解更多详情:es6.ruanyifeng.com/#docs/refle…