web-dev-qa-db-fra.com

Manière propre d'attendre le premier vrai retourné par Promise

Je travaille actuellement sur quelque chose où je lance trois promesses dans un tableau. Pour le moment ça ressemble à ça

var a = await Promise.all([Promise1(), Promise2(), Promise3()]);

Maintenant, toutes ces promesses retourneront soit vrai soit faux. Mais pour le moment, j'attends qu'ils soient tous terminés et je pourrais continuer dès que l'un d'entre eux redeviendra réalité.

J'ai pensé à des moyens d'y parvenir mais tous ont l'air un peu moche. Comment résoudriez-vous cette tâche?

21
relief.melone

Vous pouvez implémenter cette combinaison en combinant Promise.race et Promise.all:

function firstTrue(promises) {
    const newPromises = promises.map(p => new Promise(
        (resolve, reject) => p.then(v => v && resolve(true), reject)
    ));
    newPromises.Push(Promise.all(promises).then(() => false));
    return Promise.race(newPromises);
}

Testez le code ci-dessus:

function firstTrue(promises) {
  const newPromises = promises.map(p => new Promise(
    (resolve, reject) => p.then(v => v && resolve(true), reject)
  ));
  newPromises.Push(Promise.all(promises).then(() => false));
  return Promise.race(newPromises);
}

var test = values => firstTrue(
  values.map((v) => new Promise((resolve) => {
    setTimeout(() => resolve(v), Math.round(Math.random() * 1000));
  }))
).then((ret) => console.log(values, ret));

test([true, true, true]);
test([false, false, false]);
test([true, false, false]);
test([false, true, false]);
test([false, false, true]);

10
jaboja

Vous pouvez créer une nouvelle promesse qui est résolue dès qu'une promesse donnée se transforme en true comme ceci:

function promiseRaceTrue(promises) {
    return new Promise(function(resolve, reject) {
        promises.forEach(promise =>
            promise.then(val => val === true && resolve())
            // TODO handle resolve with value of "false"?
            // TODO handle rejection?
        );
        // TODO handle all resolved as "false"?
    });
}

var a = await promiseRaceTrue([Promise1(), Promise2(), Promise3()]);

Il se comporte de manière similaire à Promise.race mais ne résout que si l'une des promesses données est résolue avec la valeur true au lieu d'être résolue dès qu'une des promesses données est résolue ou rejetée.

12
str

Vous voulez fondamentalement some() .

  • Promise.all() ne fonctionnera pas car cela vous fera attendre que toutes les promesses soient complétées.
  • Promise.race() seul ne fonctionnera pas car il ne renverra que la résolution de 1 Promise.

Au lieu de cela, nous devons recevoir un tableau de promesses et continuer à les tenir jusqu'à ce que l’une d’elles revienne true. Si aucune d'entre elles n'est vraie, nous devons encore arrêter, mais nous devons noter qu'aucune d'entre elles n'est vraie.

Prenons l'exemple suivant avec le harnais de test:

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<Boolean>} 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})));
        // 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());
        });
    }
]

tests.reduce((acc, curr) => acc.then(()=>curr()), Promise.resolve());

Sortie

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

Peut utiliser Promise.race () et ne résout que les promesses initiales lorsque la valeur est true

const doSomething = (bool, val)=>{
  return new Promise((resolve, reject)=>{
     if (bool){
        resolve(val)
     }
  })
}

const promise1 = doSomething(false,'one')
const promise2 = doSomething(false,'two')
const promise3 = doSomething(true,'three')
const promise4 = doSomething(true,'four')
const promise5 = doSomething(true,'five')

Promise.race([promise1, promise2, promise3, promise4, promise5]).then(value => {
  console.log('First true one to resolve is: ', value);
  
});

7
charlietfl

Avancer hors de la réponse de str Je voudrais ajouter la possibilité de fournir un rappel pour que cela fonctionne avec plus de true/false Promises.

L'ajout d'un rappel permettra de tester le résultat de toute promesse et de renvoyer la promesse qui correspond au filtre de rappel (qui devrait renvoyer une valeur true/false).

Promise.until = function(callback, ...promises) {
  return new Promise(function(resolve, reject) {
    promises.forEach(promise =>
      promise.then(val => callback(val) === true && resolve(val))
    )
  })
}

// Create some functions that resolve true/false
function Promise1() {return new Promise(resolve => setTimeout(()=> resolve(false), 1000))}
function Promise2() {return new Promise(resolve => setTimeout(()=> resolve(true), 3000))}
function Promise3() {return new Promise(resolve => setTimeout(()=> resolve(false), 1000))}

// Create som functions that resolve objects
function Promise4() {return new Promise(resolve => setTimeout(()=> resolve({a:1}), 1000))}
function Promise5() {return new Promise(resolve => setTimeout(()=> resolve({a:2}), 3000))}
function Promise6() {return new Promise(resolve => setTimeout(()=> resolve({a:123}), 1000))}

// Create some functions that resolve strings
function Promise7() {return new Promise(resolve => setTimeout(()=> resolve('Brass'), 1000))}
function Promise8() {return new Promise(resolve => setTimeout(()=> resolve('Monkey'), 500))}
function Promise9() {return new Promise(resolve => setTimeout(()=> resolve(['Brass', 'Monkey']), 100))}

// Once one resolves `true` we will catch it
Promise.until(result => result === true, Promise1(), Promise2(), Promise3())
  .then(result => console.log(result));

// Once one resolves where `a` equals 123, we will catch it
Promise.until(result => result.a === 123, Promise4(), Promise5(), Promise6())
  .then(result => console.log(result));
  
// Once one resolves where `a` equals 123, we will catch it
Promise.until(result => result === 'Monkey', Promise7(), Promise8(), Promise9())
  .then(result => console.log(result));

5
Get Off My Lawn

OK, je vais laisser la réponse acceptée telle quelle. Mais je l'ai un peu modifié pour mes besoins, car je pensais que celui-ci était un peu plus facile à lire et à comprendre

const firstTrue = Promises => {
    return new Promise((resolve, reject) => {
        // map each promise. if one resolves to true resolve the returned promise immidately with true
        Promises.map(p => {
            p.then(result => {
                if(result === true){
                    resolve(true);
                    return;
                } 
            });
        });
        // If all promises are resolved and none of it resolved as true, resolve the returned promise with false
        Promise.all(Promises).then(() => {
            resolve(Promises.indexOf(true) !== -1);
        });   
    });    
} 
0
relief.melone