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.
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)
});
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>
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 .
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.
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)
}
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]);
}
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([]))
}
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.
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[];
}