Clean way to wait for first true returned by Promise

后端 未结 6 791
花落未央
花落未央 2021-01-01 09:51

I\'m currently working on something where I fire out three promises in an array. At the moment it looks something like this

var a = await Promise.all([Promis         


        
6条回答
  •  没有蜡笔的小新
    2021-01-01 10:30

    If you want the cutting edge solution, it sounds like you want Promise.any() which:

    Promise.any() takes an iterable of Promise objects and, as soon as one of the promises in the iterable fulfils, returns a single promise that resolves with the value from that promise. If no promises in the iterable fulfil (if all of the given promises are rejected), then the returned promise is rejected with an AggregateError, a new subclass of Error that groups together individual errors. Essentially, this method is the opposite of Promise.all().

    However, that isn't due to be usable until

    • Chrome 85
    • Firefox 79
    • Safari 14
    • No IE, Edge, or Opera support

    You basically want some().

    • Promise.all() won't work because it will make you wait for all the Promises to complete.
    • Promise.race() alone won't work because it only returns the resolution of 1 Promise.

    Instead, we need to receive an Array of Promises and keep racing them until one of them returns true at which point, we should stop. If none of them are true, we still need to stop, but we need to note that none of them were true.

    Consider the following example with test harness:

    Code

    /**
     * Promise aware setTimeout based on Angulars method
     * @param {Number} delay How long to wait before resolving
     * @returns {Promise} A resolved Promise to signal the timeout is complete
     */
    function $timeout(delay) {
        return new Promise((resolve, reject) => {
            setTimeout(() => resolve(), delay);
        });
    }
    
    /**
     * Return true (early) if any of the provided Promises are true
     * @param {Function(arg: *): Boolean} predicate The test the resolved Promise value must pass to be considered true
     * @param {Promise[]} arr The Promises to wait on
     * @returns {Promise} Whether one of the the provided Promises passed the predicate
     */
    async function some(predicate, arr) {
        // Don't mutate arguemnts
        const arrCopy = arr.slice(0);
    
        // Wait until we run out of Promises
        while(arrCopy.length){
            // Give all our promises IDs so that we can remove them when they are done
            const arrWithIDs = arrCopy.map((p, idx) => p.then(data => ({idx, data})).catch(_err => ({idx, data: false})));
            // Wait for one of the Promises to resolve
            const soon = await Promise.race(arrWithIDs);
            // If it passes the test, we're done
            if(predicate(soon.data))return true;
            // Otherwise, remove that Promise and race again
            arrCopy.splice(soon.idx, 1);
        }
        // No Promises passed the test
        return false;
    }
    
    // Test harness
    const tests = [
        function allTrue(){
            console.log(new Date());
            return some((v)=>v, [
                $timeout(1000).then(() => true),
                $timeout(2000).then(() => true),
                $timeout(3000).then(() => true)
            ]).then(d => {
                console.log(d);
                console.log(new Date());
            });
        },
        function twoSecondsTrue(){
            console.log(new Date());
            return some((v)=>v, [
                $timeout(1000).then(() => false),
                $timeout(2000).then(() => true),
                $timeout(3000).then(() => true)
            ]).then(d => {
                console.log(d);
                console.log(new Date());
            });
        },
        function threeSecondsTrue(){
            console.log(new Date());
            return some((v)=>v, [
                $timeout(1000).then(() => false),
                $timeout(2000).then(() => false),
                $timeout(3000).then(() => true)
            ]).then(d => {
                console.log(d);
                console.log(new Date());
            });
        },
        function allFalse(){
            console.log(new Date());
            return some((v)=>v, [
                $timeout(1000).then(() => false),
                $timeout(2000).then(() => false),
                $timeout(3000).then(() => false)
            ]).then(d => {
                console.log(d);
                console.log(new Date());
            });
        },
        function threeSecondsTrueWithError(){
            console.log(new Date());
            return some((v)=>v, [
                $timeout(1000).then(() => { throw new Error() }),
                $timeout(2000).then(() => false),
                $timeout(3000).then(() => true)
            ]).then(d => {
                console.log(d);
                console.log(new Date());
            });
        }
    ]
    
    tests.reduce((acc, curr) => acc.then(()=>curr()), Promise.resolve());

    Output

    // 1 Second true
    2018-07-03T18:41:33.264Z
    true
    2018-07-03T18:41:34.272Z
    
    // 2 Seconds true
    2018-07-03T18:41:34.273Z
    true
    2018-07-03T18:41:36.274Z
    
    // 3 Seconds true
    2018-07-03T18:41:36.274Z
    true
    2018-07-03T18:41:39.277Z
    
    // 3 Seconds false
    2018-07-03T18:41:39.277Z
    false
    2018-07-03T18:41:42.282Z
    
    // 3 Seconds true with error throwing
    2018-07-03T18:41:42.282Z
    true
    2018-07-03T18:41:45.285Z
    

提交回复
热议问题