Source: async.mjs

/**
 * @module kongUtilAsync
 */
import utilAsync from "./core.mjs";
import {waitForEvent} from "./event.mjs";

export * from "./core.mjs";

/**
 * Simulate `util.promisify` of `Node.js`.
 * @param {Function} func - a function taking an error-first callback as its final argument in all cases.
 * @param {boolean} [moreThanOneData=false] - wheather callback has more than one arguments having data.
 * @returns {Function} a function returns promise
 */
export function promisify(func, moreThanOneData = false) {
    return (...args) => {
        return new Promise((resolve, reject) =>
            func(...args, (err, ...data) => {
                if (err) return reject(err);
                resolve(moreThanOneData ? data : data[0]);
            })
        );
    };
}

/**
 * Resolves after `ms` milliseconds.
 * @param {integer} ms
 * @param {*} [fulfill] - what to be resolved
 * @returns {Promise} never rejects
 */
export function wait(ms, fulfill) {
    return new Promise(resolve => setTimeout(resolve, ms, fulfill));
}

/**
 * Resolves until the callback runs or the promise resolve, or rejects with an abort event until `timeout` milliseconds passed. Also a shortcut to `waitForEvent`.
 * @param {Promise | Function | Object} sth - if neither a promise nor a function, then `waitForEvent` is used.
 * @param {number | string} [timeout] non-positive number and NaN would cause the promise never reject
 * @returns {Promise}
 *
 * @example /// rejects if `fetch()` does not resolves in 1 second.
    waitFor(fetch(myURL), 1000)
    .then(resp => console.log("success"), err => console.error(err));
 *
 * @example /// similar to "promisify and run", but callbacks could be not error-first
    waitFor(cb => chrome.runtime.sendMessage(myMessage, cb))
    .then(resp => doSomething(resp));
 *
 * @example /// rejects if no click to `document.body` in 1 second
    waitFor({target: document.body, type: "click"}, 1000)
    .then(() => console.log("a click event in 1 second is detected"));
 *
 * @example /// same as above
    waitForEvent(document.body, "click", 1000)
    .then(() => console.log("a click event in 1 second is detected"));
 *
 */
export function waitFor(sth, timeout) {
    let exe;
    if (sth instanceof Promise) exe = r => sth.then(r);
    else if (sth instanceof Function) exe = r => sth(r);
    else {
        if (sth instanceof EventTarget) return waitForEvent(...arguments);
        const {target, type, ...options} = sth;
        options.timeout = timeout;
        return waitForEvent(target, type, options);
    }
    if (exe) return new Promise((resolve, reject) => {
        setTimeout(exe, 0, resolve);
        if (timeout > 0) setTimeout(reject, timeout, new Event("abort"));
    });
}

/**
 * Keeps calling `asyncFunc` until it returns true to resolve; or timeout to reject.
 * @param {Function} asyncFunc - a function returns a promise
 * @param {number} checkInterval - in milliseconds
 * @param {number} timeout - in milliseconds; non-positive means forever.
 */
export async function waitUntilTrue(asyncFunc, checkInterval = 100, timeout = 0) {
    return new Promise(async (resolve, reject) => {
        let result;
        if (result = await asyncFunc()) return resolve(result);

        let timeoutID;
        const wrapper = async function () {
            if (result = await asyncFunc()) return resolve(result);
            timeoutID = setTimeout(wrapper, checkInterval);
        };
        timeoutID = setTimeout(wrapper, checkInterval);

        if (timeout > 0) {
            setTimeout(() => {
                clearTimeout(timeoutID);
                reject();
            }, timeout);
        }
    });
}

/**
 * Convert an async function by appending an argument represents time limit.
 * @param {Function} asyncFunc - a function returns a promise
 * @param {number} timeout - in milliseconds
 * @returns {Function} receiving same arguments as the origin, but the returned promise would auto-reject after `timeout` milliseconds
 *
 * @example /// convert `fetch()` to auto-reject after 3 seconds.
    const fetchAutoReject = addTimeLimit(fetch, 3000);
 *
 */
export function addTimeLimit(asyncFunc, timeout) {
    return function(...args) {
        const promise = asyncFunc(...args);
        return waitFor(promise, timeout);
    };
}


Object.assign(utilAsync, {
    promisify, wait, waitFor, waitUntilTrue, addTimeLimit
});

export default utilAsync;