NodeBB/NodeBB

View on GitHub
src/promisify.js

Summary

Maintainability
A
35 mins
Test Coverage
'use strict';

const util = require('util');

module.exports = function (theModule, ignoreKeys) {
    ignoreKeys = ignoreKeys || [];
    function isCallbackedFunction(func) {
        if (typeof func !== 'function') {
            return false;
        }
        const str = func.toString().split('\n')[0];
        return str.includes('callback)');
    }

    function isAsyncFunction(fn) {
        return fn && fn.constructor && fn.constructor.name === 'AsyncFunction';
    }

    function promisifyRecursive(module) {
        if (!module) {
            return;
        }

        const keys = Object.keys(module);
        keys.forEach((key) => {
            if (ignoreKeys.includes(key)) {
                return;
            }
            if (isAsyncFunction(module[key])) {
                module[key] = wrapCallback(module[key], util.callbackify(module[key]));
            } else if (isCallbackedFunction(module[key])) {
                module[key] = wrapPromise(module[key], util.promisify(module[key]));
            } else if (typeof module[key] === 'object') {
                promisifyRecursive(module[key]);
            }
        });
    }

    function wrapCallback(origFn, callbackFn) {
        return async function wrapperCallback(...args) {
            if (args.length && typeof args[args.length - 1] === 'function') {
                const cb = args.pop();
                args.push((err, res) => (res !== undefined ? cb(err, res) : cb(err)));
                return callbackFn(...args);
            }
            return origFn(...args);
        };
    }

    function wrapPromise(origFn, promiseFn) {
        return function wrapperPromise(...args) {
            if (args.length && typeof args[args.length - 1] === 'function') {
                return origFn(...args);
            }

            return promiseFn(...args);
        };
    }

    promisifyRecursive(theModule);
};