web-dev-qa-db-fra.com

Filtrer un tableau avec une fonction qui retourne une promesse

Donné

let arr = [1,2,3];

function filter(num) {
  return new Promise((res, rej) => {
    setTimeout(() => {
      if( num === 3 ) {
        res(num);
      } else {
        rej();
      }
    }, 1);
  });
 }

 function filterNums() {
   return Promise.all(arr.filter(filter));
 }

 filterNums().then(results => {
   let l = results.length;
   // length should be 1, but is 3
 });

La longueur est 3 car les promesses sont retournées, pas les valeurs. Existe-t-il un moyen de filtrer le tableau avec une fonction qui retourne une promesse? 

Remarque: Pour cet exemple, fs.stat a été remplacé par setTimeout, voir https://github.com/silenceisgolden/learn-esnext/blob/array-filter-async-function/tutorials/array-filter-with- async-function.js pour le code spécifique.

25
ajklein

Comme mentionné dans les commentaires, Array.prototype.filter est synchrone et ne prend donc pas en charge Promises.

Puisque vous pouvez maintenant (théoriquement) sous-classer les types intégrés avec ES6, vous devriez pouvoir ajouter votre propre méthode asynchrone qui enveloppe la fonction de filtre existante:

Note: J'ai commenté le sous-classement, parce que Babel ne le supporte pas encore pour Arrays

class AsyncArray /*extends Array*/ {
  constructor(arr) {
    this.data = arr; // In place of Array subclassing
  }

  filterAsync(predicate) {
     // Take a copy of the array, it might mutate by the time we've finished
    const data = Array.from(this.data);
    // Transform all the elements into an array of promises using the predicate
    // as the promise
    return Promise.all(data.map((element, index) => predicate(element, index, data)))
    // Use the result of the promises to call the underlying sync filter function
      .then(result => {
        return data.filter((element, index) => {
          return result[index];
        });
      });
  }
}
// Create an instance of your subclass instead
let arr = new AsyncArray([1,2,3,4,5]);
// Pass in your own predicate
arr.filterAsync(async (element) => {
  return new Promise(res => {
    setTimeout(() => {
      res(element > 3);
    }, 1);
  });
}).then(result => {
  console.log(result)
});

Babel REPL Démo

18
CodingIntrigue

Voici un moyen:

var wait = ms => new Promise(resolve => setTimeout(resolve, ms));
var filter = num => wait(1).then(() => num == 3);

var filterAsync = (array, filter) =>
  Promise.all(array.map(entry => filter(entry)))
  .then(bits => array.filter(entry => bits.shift()));

filterAsync([1,2,3], filter)
.then(results => console.log(results.length))
.catch(e => console.error(e));

La fonction filterAsync prend un tableau et une fonction qui doit renvoyer true ou false ou renvoyer une promesse résolue en true ou false (ce que vous avez demandé (presque, je n'ai pas surchargé le rejet d'une promesse car je pense que c'est une mauvaise idée). Faites-moi savoir si vous avez des questions à ce sujet.

var wait = ms => new Promise(resolve => setTimeout(resolve, ms));
var filter = num => wait(1).then(() => num == 3);

var filterAsync = (array, filter) =>
  Promise.all(array.map(entry => filter(entry)))
  .then(bits => array.filter(entry => bits.shift()));

filterAsync([1,2,3], filter)
.then(results => console.log(results.length))
.catch(e => console.error(e));

var console = { log: msg => div.innerHTML += msg + "<br>",
                error: e => console.log(e +", "+ (e.lineNumber-25)) };
<div id="div"></div>

23
jib

Voici une solution élégante en 2017 utilisant async/wait:

Utilisation très simple:

const results = await filter(myArray, async num => {
  await doAsyncStuff()
  return num > 2
})

La fonction d'assistance (copiez-la dans votre page Web):

async function filter(arr, callback) {
  const fail = Symbol()
  return (await Promise.all(arr.map(async item => (await callback(item)) ? item : fail))).filter(i=>i!==fail)
}

Démo:

// Async IIFE
(async function() {
  const myArray = [1, 2, 3, 4, 5]

  // This is exactly what you'd expect to write 
  const results = await filter(myArray, async num => {
    await doAsyncStuff()
    return num > 2
  })

  console.log(results)
})()


// Arbitrary asynchronous function
function doAsyncStuff() {
  return Promise.resolve()
}


// The helper function
async function filter(arr, callback) {
  const fail = Symbol()
  return (await Promise.all(arr.map(async item => (await callback(item)) ? item : fail))).filter(i=>i!==fail)
}

Je vais même jeter un CodePen .

18
Gabe Rogan

Promise Réducteur à la rescousse!

[1, 2, 3, 4].reduce((op, n) => {
    return op.then(filteredNs => {
        return new Promise(resolve => {
            setTimeout(() => {
                if (n >= 3) {
                    console.log("Keeping", n);
                    resolve(filteredNs.concat(n))
                } else {
                    console.log("Dropping", n);
                    resolve(filteredNs);
                }
            }, 1000);
        });
    });
}, Promise.resolve([]))
.then(filteredNs => console.log(filteredNs));

Les réducteurs sont géniaux. "Réduire mon problème à mon objectif" semble être une très bonne stratégie pour tout ce qui est plus complexe que ce que les outils simples vont résoudre pour vous, c’est-à-dire filtrer un éventail de choses qui ne sont pas toutes disponibles immédiatement.

9
Dan Ross

En retard au jeu, mais personne n’en ayant parlé, Bluebird prend en charge Promise.map, qui est ma meilleure solution pour les filtres nécessitant un traitement aysnc pour la condition,

function filterAsync(arr) {
    return Promise.map(arr, num => {
        if (num === 3) return num;
    })
        .filter(num => num !== undefined)
}
2
Spencer MacBeth

Pour TypeScript folk (ou es6, supprimez simplement la syntaxe de type)

function mapAsync<T, U>(array: T[], callbackfn: (value: T, index: number, array: T[]) => Promise<U>): Promise<U[]> {
  return Promise.all(array.map(callbackfn));
}

async function filterAsync<T>(array: T[], callbackfn: (value: T, index: number, array: T[]) => Promise<boolean>): Promise<T[]> {
  const filterMap = await mapAsync(array, callbackfn);
  return array.filter((value, index) => filterMap[index]);
}

es6

function mapAsync(array, callbackfn) {
  return Promise.all(array.map(callbackfn));
}

async function filterAsync(array, callbackfn) {
  const filterMap = await mapAsync(array, callbackfn);
  return array.filter((value, index) => filterMap[index]);
}
1
William Lohan

Une variante de @ DanRoss:

async function filterNums(arr) {
  return await arr.reduce(async (res, val) => {
    res = await res
    if (await filter(val)) {
      res.Push(val)
    }
    return res
  }, Promise.resolve([]))
}

Notez que si (comme dans le cas présent) vous n'avez pas à vous soucier de filter () ayant les effets secondaires Qui doivent être sérialisés, vous pouvez aussi faire:

async function filterNums(arr) {
  return await arr.reduce(async (res, val) => {
    if (await filter(val)) {
      (await res).Push(val)
    }
    return res
  }, Promise.resolve([]))
}
0
shaunc

Un moyen valable de le faire (mais cela semble trop compliqué):

let arr = [1,2,3];

function filter(num) {
  return new Promise((res, rej) => {
    setTimeout(() => {
      if( num === 3 ) {
        res(num);
      } else {
        rej();
      }
    }, 1);
  });
}

async function check(num) {
  try {
    await filter(num);
    return true;
  } catch(err) {
    return false;
  }
}

(async function() {
  for( let num of arr ) {
    let res = await check(num);
    if(!res) {
      let index = arr.indexOf(num);
      arr.splice(index, 1);
    }
  }
})();

Encore une fois, semble beaucoup trop en désordre.

0
ajklein

Si quelqu'un s'intéresse à la solution TypeScript moderne (avec le symbole d'échec utilisé pour le filtrage):

const failSymbol = Symbol();

export async function filterAsync<T>(
  itemsToFilter: T[],
  filterFunction: (item: T) => Promise<boolean>,
): Promise<T[]> {
  const itemsOrFailFlags = await Promise.all(
    itemsToFilter.map(async (item) => {
      const hasPassed = await filterFunction(item);

      return hasPassed ? item : failSymbol;
    }),
  );

  return itemsOrFailFlags.filter(
    (itemOrFailFlag) => itemOrFailFlag !== failSymbol,
  ) as T[];
}
0
pie6k