Given
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
});
The length is 3 because Promises are returned, not values. Is there a way to filter the array with a function that returns a Promise?
Note: For this example, fs.stat has been replaced with setTimeout, see https://github.com/silenceisgolden/learn-esnext/blob/array-filter-async-function/tutorials/array-filter-with-async-function.js for the specific code.
Here is a 2017 elegant solution using async/await :
Very straightforward usage:
const results = await filter(myArray, async num => {
await doAsyncStuff()
return num > 2
})
The helper function (copy this into your web page):
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)
}
Demo:
// 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)
}
I'll even throw in a CodePen.
As mentioned in the comments, Array.prototype.filter is synchronous and therefore does not support Promises.
Since you can now (theoretically) subclass built-in types with ES6, you should be able to add your own asynchronous method which wraps the existing filter function:
Note: I've commented out the subclassing, because it's not supported by Babel just yet for 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 Demo
For typescript folk (or es6 just remove type syntax)
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]);
}
es5
function mapAsync(array, callbackfn) {
return Promise.all(array.map(callbackfn));
}
function filterAsync(array, callbackfn) {
return mapAsync(array, callbackfn).then(filterMap => {
return array.filter((value, index) => filterMap[index]);
});
}
edit: demo
function mapAsync(array, callbackfn) {
return Promise.all(array.map(callbackfn));
}
function filterAsync(array, callbackfn) {
return mapAsync(array, callbackfn).then(filterMap => {
return array.filter((value, index) => filterMap[index]);
});
}
var arr = [1, 2, 3, 4];
function isThreeAsync(number) {
return new Promise((res, rej) => {
setTimeout(() => {
res(number === 3);
}, 1);
});
}
mapAsync(arr, isThreeAsync).then(result => {
console.log(result); // [ false, false, true, false ]
});
filterAsync(arr, isThreeAsync).then(result => {
console.log(result); // [ 3 ]
});
Here's a way:
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));
The filterAsync function takes an array and a function that must either return true or false or return a promise that resolves to true or false, what you asked for (almost, I didn't overload promise rejection because I think that's a bad idea). Let me know if you have any questions about it.
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>
Promise Reducer to the rescue!
[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));
Reducers are awesome. "Reduce my problem to my goal" seems to be a pretty good strategy for anything more complex than what the simple tools will solve for you, i.e. filtering an array of things that aren't all available immediately.
asyncFilter method:
Array.prototype.asyncFilter = async function(f){
var array = this;
var booleans = await Promise.all(array.map(f));
return array.filter((x,i)=>booleans[i])
}
Late to the game but since no one else mentioned it, Bluebird supports Promise.map which is my go-to for filters requiring aysnc processing for the condition,
function filterAsync(arr) {
return Promise.map(arr, num => {
if (num === 3) return num;
})
.filter(num => num !== undefined)
}
Two lines, completely typesafe
export const asyncFilter = async <T>(list: T[], predicate: (t: T) => Promise<boolean>) => {
const resolvedPredicates = await Promise.all(list.map(predicate));
return list.filter((item, idx) => resolvedPredicates[idx]);
};
In case someone is interested in modern typescript solution (with fail symbol used for filtering):
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[];
}
There is a one liner to to do that.
const filterPromise = (values, fn) =>
Promise.all(values.map(fn)).then(booleans => values.filter((_, i) => booleans[i]));
Pass the array into values and the function into fn.
More description on how this one liner works is available here.
For production purposes you probably want to use a lib like lodasync:
import { filterAsync } from 'lodasync'
const result = await filterAsync(async(element) => {
await doSomething()
return element > 3
}, array)
Under the hood, it maps your array by invoking the callback on each element and filters the array using the result. But you should not reinvent the wheel.
You can do something like this...
theArrayYouWantToFilter = await new Promise(async (resolve) => {
const tempArray = [];
theArrayYouWantToFilter.filter(async (element, index) => {
const someAsyncValue = await someAsyncFunction();
if (someAsyncValue) {
tempArray.push(someAsyncValue);
}
if (index === theArrayYouWantToFilter.length - 1) {
resolve(tempArray);
}
});
});
Wrapped within an async function...
async function filter(theArrayYouWantToFilter) {
theArrayYouWantToFilter = await new Promise(async (resolve) => {
const tempArray = [];
theArrayYouWantToFilter.filter(async (element, index) => {
const someAsyncValue = await someAsyncFunction();
if (someAsyncValue) {
tempArray.push(someAsyncValue);
}
if (index === theArrayYouWantToFilter.length - 1) {
resolve(tempArray);
}
});
});
return theArrayYouWantToFilter;
}
A valid way to do this (but it seems too messy):
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);
}
}
})();
Again, seems way too messy.
A variant of #DanRoss's:
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([]))
}
Note that if (as in current case) you don't have to worry about filter() having
side effects that need to be serialized, you can also do:
async function filterNums(arr) {
return await arr.reduce(async (res, val) => {
if (await filter(val)) {
(await res).push(val)
}
return res
}, Promise.resolve([]))
}
Late to the party, and I know that my answer is similar to other already posted answers, but the function I'm going to share is ready for be dropped into any code and be used.
As usual, when you have to do complex operations on arrays, reduce is king:
const filterAsync = (asyncPred) => arr =>
arr.reduce(async (acc,item) => {
const pass = await asyncPred(item);
if(pass) (await acc).push(item);
return acc;
},[]);
It uses modern syntax so make sure your target supports it. To be 100% correct you should use Promise.resolve([]) as the initial value, but JS just doesn't care and this way it is way shorter.
Then you can use it like this:
var wait = ms => new Promise(resolve => setTimeout(resolve, ms));
const isOdd = x => wait(1).then(()=>x%2);
(filterAsync(isOdd)([1,2,3,4,4])).then(console.log) // => [1,3]
Here's a shorter version of #pie6k's Typescript version:
async function filter<T>(arr: T[], callback: (val: T) => Promise<Boolean>) {
const fail = Symbol()
const result = (await Promise.all(arr.map(async item => (await callback(item)) ? item : fail))).filter(i => i !== fail)
return result as T[] // the "fail" entries are all filtered out so this is OK
}
An efficient way of approaching this is by processing arrays as iterables, so you can apply any number of required operations in a single iteration.
The example below uses library iter-ops for that:
import {pipe, filter, toAsync} from 'iter-ops';
const arr = [1, 2, 3]; // synchronous iterable
const i = pipe(
toAsync(arr), // make our iterable asynchronous
filter(async (value, index) => {
// returns Promise<boolean>
})
);
(async function() {
for await (const a of i) {
console.log(a); // print values
}
})();
All operators within the library support asynchronous predicates when inside an asynchronous pipeline (why we use toAsync), and you can add other operators, in the same way.
Use of Promise.all for this is quite inefficient, because you block the entire array from any further processing that can be done concurrently, which the above approach allows.
I've tried to get a tree node after search process. But it always return undefined..
here's the code.
const findOrder = (list, key) =>
new Promise((resolve) => {
for (let i = 0; i < list.length; i++) {
// find node through key in tree
if (list[i].key === key) {
console.log(`================I got it!: `, list[i].children); // children[]
resolve(list[i].children);
}
if (list[i].children) {
findOrder(list[i].children, key);
}
}
});
const setOrder = async() => {
const orders = await findOrder(
treeData,
dropObj.classKind === "1" ? keyGen(dropObj.key) : dropObj.key
);
console.log(`==================siblings: `, orders); // undefined
};
setOrder();
what is the problem?
You did not resolve it here,
// ...
if (list[i].children) {
findOrder(list[i].children, key);
}
// ...
To let the outer Promise know when to resolve it, you should explicitly do it:
// ...
if (list[i].children) {
findOrder(list[i].children, key).then(result => {
// as resolve can only be called once,
// do not call it if it doesn't found anything
if (result) resolve(result)
});
}
// ...
This should work. However, this implementation has too many useless calls to 'resolve'. It's better to find the matched item directly and resolve it.
Here is an example:
const findOrder = function (list, key) {
return new Promise((resolve, reject) => {
resolve(find(list, key))
function find (_list, key) {
for (let i = 0; i < _list.length; i++) {
if (_list[i].key === key) return _list[i].children
if (_list[i].children) {
const c = find(_list[i].children, key)
if (c) return c
}
}
return undefined
}
})
}
Don't know if the terminology is correct, but I have an array of objects, which also has other arrays in it. I need to go through each of these items. If the operation wasn't async it would look something like this:
myArray.forEach(x => {
x.otherArray.forEach(y => {
doSomething(y)
})
})
However the doSomething function is async, and unfortunately I am well aware that during these iterations I can't simply through a couple asyncs and awaits to make it work.
Usually, when I need to do promises during a iteration, I do the following:
await myArray.reduce((p, item) => {
return p.then(() => {
return doAsyncSomething(item)
})
}, Promise.resolve())
But because I am doing two iterations at once, this becomes a bit more complicated, so how do I go about it?
I currently have something like this, but it doesn't seem to be the right way:
await myArray.reduce((p, item) => {
return item.someArray.reduce((promise, it, index) => {
return promise.then(() => {
return doAsyncSomething()
})
}, Promise.resolve())
}, Promise.resolve())
I know I could just organize my objects into an array through the two forEach and then do the reduce with the doSomething in it, but I doubt it's the most efficient or elegant way of getting it done. So how could I do this?
try this:
let objArray = [ {otherArray: [1,2]}, {otherArray: [3,4]}, {otherArray: [5,6]} ];
function doAsyncSomething(item) {
return Promise.resolve(item);
}
async function doit() {
let s = 0;
for(const x of objArray)
for(const y of x.otherArray)
s+= await doAsyncSomething(y);
return s;
}
doit().then(v => {
console.log(v);
});
or try recurcive call like this:
let objArray = [ {otherArray: [1,2]}, {otherArray: [3,4]}, {otherArray: [5,6]} ];
let index = 0;
let subIndex = 0;
function doAsyncSomething(item) {
return new Promise(resolve => {
console.log("proc item", item);
resolve(item);
});
}
async function doit() {
return await doAsyncSomething(objArray[index].otherArray[subIndex]);
}
function go() {
doit().then(v => {
console.log(v);
subIndex++;
if (subIndex >= objArray[index].otherArray.length) {
subIndex = 0;
index++;
}
if (index < objArray.length)
go();
});
}
Assuming you want all operations to happen in parallel, you can use Promise.all():
async function () { // I assume you already have this
// ...
let asyncOps = [];
myArray.forEach(x => {
x.otherArray.forEach(y => {
asyncOps.push(doSomething(y));
})
})
await Promise.all(asyncOps);
}
function doSomething (x) {
return new Promise((ok,fail) =>
setTimeout(() => {
console.log(x);
ok();
},10));
}
let foo = [[1,2,3,4],[5,6,7,8]];
async function test() {
let asyncOps = [];
foo.forEach(x =>
x.forEach(y =>
asyncOps.push(doSomething(y))));
await Promise.all(asyncOps);
}
test();
If you want to do the async operations sequentially it's even simpler:
async function () { // I assume you already have this
// ...
for (let i=0; i<myArray.length; i++) {
let x = myArray[i];
for (let j=0; j<x.length; j++) {
let y = x[j];
await doSomething(y);
}
}
}
Pass on the promise into the inner loop when reducing:
await myArray.reduce((p, item) =>
item.someArray.reduce((p, it, index) =>
p.then(() => doAsyncSomething(it)),
p // <<<
),
Promise.resolve()
)
Or I'd prefer:
for(const { someArray } of myArray) {
for(const it of someArray) {
await doSomethingAsync(it);
}
}
If you want to run the tasks in parallel:
await Promise.all(
myArray.flatMap(item => item.someArray.map(doSomethingAsnyc))
);
I found a recursive expression in a library very confused.
The code is here :
https://github.com/tappleby/redux-batched-subscribe/blob/master/src/index.js#L22
export function batchedSubscribe(batch) {
if (typeof batch !== 'function') {
throw new Error('Expected batch to be a function.');
}
const listeners = [];
function subscribe(listener) {
listeners.push(listener);
return function unsubscribe() {
const index = listeners.indexOf(listener);
listeners.splice(index, 1);
};
}
function notifyListenersBatched() {
batch(() => listeners.slice().forEach(listener => listener()));
}
return next => (...args) => {
const store = next(...args);
const subscribeImmediate = store.subscribe;
function dispatch(...dispatchArgs) {
const res = store.dispatch(...dispatchArgs);
notifyListenersBatched();
return res;
}
return {
...store,
dispatch,
subscribe,
subscribeImmediate
};
};
}
Specifically this part:
return next => (...args) => {
const store = next(...args);
const subscribeImmediate = store.subscribe;
function dispatch(...dispatchArgs) {
const res = store.dispatch(...dispatchArgs);
notifyListenersBatched();
return res;
}
return {
...store,
dispatch,
subscribe,
subscribeImmediate
};
};
How is this not infinite recursion?
How is this not infinite recursion?
There is absolutely no recursion here. The syntax next => (...args) => … does not translate to
return function next(...args) {
const store = next(...args);
…
but rather to
return function(next) {
return function(...args) {
const store = next(...args);
…
So unless the caller of that function does something weird like var f = batchedSubscribe(…); f(f)(f)…;, it won't call itself.
The reason we both seemed confused by this is because the arrow function, if written as a single statement, implicitly calls return.
So for example a simple function like this:
const add = (a, b) => a + b;
is equivalent to
var add = function(a, b) {
return a + b;
}
Knowing this we can remove the sugar and convert the arrow functions:
return next => function(...args) { // body }
Is really what's going on here, and if we go one step further we get this:
return function(next) {
return function(...args) {
const store = next(...args);
const subscribeImmediate = store.subscribe;
function dispatch(...dispatchArgs) {
const res = store.dispatch(...dispatchArgs);
notifyListenersBatched();
return res;
}
return {
...store,
dispatch,
subscribe,
subscribeImmediate
};
}
}
Both functions that are containing the code are actually nameless. next is a function, but not one of the functions being returned. It is passed as a variable into the first returned function.
There's no recursion here, but rather a lot of function composition, which is to be expected from a library like redux that draws so much from functional programming.