3

考虑如下代码

whatever.onclick = async () => {
    const a = await(await fetch('step-1')).text();
    const b = await(await fetch('step-2')).text();
    whatever.textContent = a + b;
}

如果用户在step-1step-2之间再次点击的话,就有可能同时发出两个step-1

当然,服务器可以验证之后通通拒掉,但是用户体验很差。这是个很合理的需求,所以我特意在SF上提问,可惜看起来并没有现成的轮子可以用。

所以还是只能自己造。


用下面的函数包裹原函数,如果前一次请求尚未结束,新请求会和旧请求一起返回。

/**
 * Creates a function that invokes `originalFunction`, with the `this` binding
 * and `arguments` of the created function, while there is no other pending 
 * excutions of `originalFunction`. Simultaneous calls to the created function
 * return the result of the first pending `originalFunction` invocation.
 * 
 * @param {function} originalFunction async function to wrap
 */
const debounceAsync = originalFunction => {
    let currentExcution = null;
    const wrappedFunction = async function () {
        // 1. locked => return lock
        if (currentExcution) return currentExcution;

        // 2. released => apply
        currentExcution = originalFunction.apply(this, arguments);
        try {
            return await currentExcution;
        }
        finally {
            currentExcution = null;
        }
    };
    return wrappedFunction;
};

用下面的函数包裹原函数,如果前一次请求尚未结束,新请求会排队。

const endOfQueue = Promise.resolve();
const overrideResult = async lastExcution => {
    try {
        await lastExcution;
    }
    finally {
        return endOfQueue;
    }
}

/**
 * Creates a function that invokes `originalFunction`, with the `this` binding
 * and `arguments` of the created function, while there is no other pending 
 * excutions of `originalFunction`. Simultaneous calls to the created function
 * will be queued up.
 * 
 * @param {function} originalFunction async function to wrap
 */
const queueAsync = originalFunction => {
    let lastExcution = endOfQueue;
    const wrappedFunction = async function () {
        // 1. queue up
        const myExcution = lastExcution.then(() => originalFunction.apply(this, arguments));

        // 2. update queue tail + swipe excution result from queue
        lastExcution = overrideResult(myExcution);

        // 3. return excution result
        return myExcution;
    };
    return wrappedFunction;
}

示例使用

/**
 * A promisified settimeout
 * 
 * @param {number} [ms=0] time to sleep in ms
 */
const sleep = (ms = 0) => new Promise(resolve => setTimeout(resolve, ms));

const debounceAsync_UNIT_TEST = async () => {
    const goodnight = debounceAsync(sleep);
    for (let i = 0; i < 8; i++) {
        goodnight(5000).then(() => console.log(Date()));
        await sleep(500);
    }
    console.warn('Expected output: 8 identical datetime');
};

const queueAsync_UNIT_TEST = () => {
    const badnight = queueAsync(i => sleep(i).then(() => { if (Math.random() > 0.5) throw new Error('uncaught error test: you should expect a console error message.') }));
    badnight(1000);
    badnight(1000);
    badnight(1000);
    badnight(1000);
    badnight(1000).finally(() => console.log('5s!'));
    badnight(1000);
    badnight(1000);
    badnight(1000);
    badnight(1000);
    badnight(1000).finally(() => console.log('10s!'));
    console.warn('Check message timestamps.');
    console.warn('Bad:');
    console.warn('1 1 1 1 1:5s');
    console.warn(' 1 1 1 1 1:10s');
    console.warn('Good:');
    console.warn('1 1 1 1 1:5s');
    console.warn('         1 1 1 1 1:10s');
}

以上所有代码按Mozilla Public License, v. 2.0授权。
以上所有文字内容按CC BY-NC-ND 4.0授权。


liqi0816
1.9k 声望578 粉丝