Interviewer: please implement a general function to turn callback into promise

Posted by TreeNode on Thu, 18 Nov 2021 08:04:55 +0100

After reading this article, you will learn:

1. Node What module is used for loading
2. obtain git Warehouse owned tags Principle of
3. Learn to debug and see the source code
4. Learn to interview high-frequency test sites promisify Principle and implementation of
5. wait

At the beginning, don't rush to see the source code of thousands of lines and tens of thousands of lines. The longer the source code length, the harder it is to stick to it. Look at the source code, pay attention to step by step. For example, start with what you can use.

I have answered similar questions on knowledge before.

What if you don't understand the source code of the front-end framework within one year?

In short, look at the source code

proceed in an orderly way and step by step
 With the help of debugging
 Clear the main line
 Access to information
 Summary record

2. Use

import remoteGitTags from 'remote-git-tags';

console.log(await remoteGitTags(''));
//=> Map {'3.0.5' => '6020cc35c027e4300d70ef43a3873c8f15d1eeb2', ...}

3. Source code

Get tags from a remote Git repo

The function of this library is to obtain all tags from the remote warehouse.

Principle: get tags by executing git LS remote -- tags repourl (warehouse Path)

Application scenario: you can see which packages depend on this package. npm package description information [2]

One of the familiar ones is NPM check updates [3]

NPM check updates upgrades your package.json dependency to the latest version, ignoring the specified version.

Another scenario may be to obtain all tags information in github, switch tags or select the release version of tags, such as wechat applet version.

Look at the package.json file before looking at the source code.

3.1 package.json

// package.json
  // Specify which module the Node is loaded with. commonjs is used by default
 "type": "module",
 "exports": "./index.js",
  // Specifies the version of nodejs
 "engines": {
  "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
 "scripts": {
  "test": "xo && ava"

As we all know, node has always been the CommonJS module mechanism. Node 13 adds support for the standard ES6 module.

The easiest way to tell a Node what module it wants to load is to encode the information into different extensions. If it is a file ending in. mjs, Node will always load it as an ES6 module. If it is a file ending in. cjs, Node will always load it as a CommonJS module.

For files ending in. js, the default is the commonjs module. If the package.json file exists in the peer directory and all directories, and the type attribute is module, the ES6 module is used. If the type value is commonjs or empty or there is no package.json file, it is loaded by the default commonjs module.

The Node module loading method is described in more detail in section 16.1.4 Node module of JavaScript authoritative guide version 7. Chapter 16 of this book is about Node, which can be consulted by interested readers.

3.2 debugging source code

# It is recommended to clone my project to ensure synchronization with the article and complete test files
git clone
# npm i -g yarn
cd remote-git-tags && yarn
# VSCode directly opens the current project
# code .

# Or clone the official project
git clone
# npm i -g yarn
cd remote-git-tags && yarn
# VSCode directly opens the current project
# code .

Open the project with the latest VSCode and find the test command in the scripts attribute of package.json. Hover over the test command and the options of run command and debug command will appear. Select the debug command.

Commissioning is shown in the figure:

Commissioning is shown in the figure

The description of VSCode debugging Node.js is shown in the following figure:

VSCode debugging Node.js description

See this article for more debugging details Novice: front end programmers must learn basic skills - debugging JS code.

After debugging, let's look at the main file.

3.3 the main file has only 22 lines of source code

// 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;

The source code is actually easy to understand at a glance.

3.4 git ls-remote --tags

Support remote warehouse links.

Git LS remote document [4]

As shown in the figure below:


Get all tags git LS remote -- Tags

Store all tags and corresponding hash values in the Map object.

3.5 node:util

Node document [5]

Core modules can also be identified using the node: prefix, in which case it bypasses the require cache. For instance, require('node:http') will always return the built in HTTP module, even if there is require.cache entry by that name.

That is, a reference to the node native library can be prefixed with node: for example, import util from 'node:util'

Seeing this, I understand the principle. After all, there are only 22 lines of code. Then talk about promise.

4. promisify

There is a paragraph in the source code:

const execFile = promisify(childProcess.execFile);

Some readers may not understand it very well.

Next, focus on the implementation of this function.

The promise function converts the callback form to promise form.

We know that Node.js is inherently asynchronous and writes code in the form of error callback. The first argument to the callback function is the error message. That is, error first.

Let's look at it in a simple scenario.

4.1 simple implementation

Suppose we have a need to load pictures with JS. We found pictures from this website [6].

const imageSrc = '';

function loadImage(src, callback) {
    const image = document.createElement('img');
    image.src = src;
    image.alt = 'The official account is a special view of Sichuan.'; = 'width: 200px;height: 200px';
    image.onload = () => callback(null, image);
    image.onerror = () => callback(new Error('Loading failed'));

It's easy to write the above code, and it's also easy to write the code of the callback function. The demand is done.

loadImage(imageSrc, function(err, content){

However, the callback function has problems such as callback hell. We then use promise to optimize it.

4.2 promise preliminary optimization

We can also easily write the following code implementation.

const loadImagePromise = function(src){
    return new Promise(function(resolve, reject){
        loadImage(src, function (err, image) {
loadImagePromise(imageSrc).then(res => {
.catch(err => {

But this is not universal. We need to encapsulate a more general promise function.

4.3 general promise function

function promisify(original){
    function fn(...args){
        return new Promise((resolve, reject) => {
            args.push((err, ...values) => {
                    return reject(err);
            // original.apply(this, args);
            Reflect.apply(original, this, args);
    return fn;

const loadImagePromise = promisify(loadImage);
async function load(){
        const res = await loadImagePromise(imageSrc);

The demand is done. At this time, it is more common.

These examples are stored in the examples folder in my warehouse. You can clone it, NPX HTTP server. Run the service and try it.


For the result of running failure, you can change imageSrc to a non-existent image.

Promise can be said to be a high-frequency examination site for interview. Many interviewers like this question.

Next, let's look at the implementation of promise in the Node.js source code.

4.4 node utils promise source code

github1s node utils source code [7]

The source code will not be explained too much for the time being. You can refer to the documentation. Combined with the previous examples, it is also easy to understand.

Utils promise document [8]

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
  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];
        } else {
      ReflectApply(original, this, args);

  ObjectSetPrototypeOf(fn, ObjectGetPrototypeOf(original));

  ObjectDefineProperty(fn, kCustomPromisifiedSymbol, {
    value: fn, enumerable: false, writable: false, configurable: true
  return ObjectDefineProperties(

promisify.custom = kCustomPromisifiedSymbol;

5. ES6 + and other knowledge

This paper involves Map, for of, regularization, deconstruction and assignment.

There are also functions involving encapsulation, such as ReflectApply, ObjectSetPrototypeOf, ObjectDefineProperty, ObjectGetOwnPropertyDescriptors, which are basic knowledge.

For this knowledge, you can refer to esma specification [9], or teacher Ruan Yifeng's ES6 introductory tutorial [10] and other books.

6. Summary

In one sentence, briefly describe the principle of remote git Tags: use the child process of Node.js_ The execFile method of the process module executes git LS remote -- tags repourl to obtain all tags and the corresponding hash values of tags and store them in the Map object.

This paper describes that we can look at the source code step by step with the help of debugging, clarifying the main line, consulting materials and summarizing records.

Through the 22 line code warehouse of remote git tags, I learned what module was used for Node loading, knew that the original git LS remote tags supported remote warehouse, learned the principle and source code implementation of the promise function of high-frequency interview test points, and consolidated some basic knowledge such as ES6 +.

It is recommended that readers clone my warehouse [11] and practice debugging source code learning.

You can also take a look at the implementation of the ES6 promise [12] library later.

reference material

[1] In this paper, we use remote git tags analysis to find a star ^ ^:

[2]npm package description information:


[4] Git LS remote document:

[5]Node document:

[6] This site:

[7]github1s node utils source code:

[8] Utils promise document:

[9]esma specification:

[10] ES6 Getting Started tutorial:

[11] My warehouse: