Filtering an array with a function that returns a promise

前端 未结 15 1249
遇见更好的自我
遇见更好的自我 2020-11-28 13:22

Given

let arr = [1,2,3];

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


        
相关标签:
15条回答
  • 2020-11-28 13:39

    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.

    0 讨论(0)
  • 2020-11-28 13:44

    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.

    0 讨论(0)
  • 2020-11-28 13:47

    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>

    0 讨论(0)
  • 2020-11-28 13:49

    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.

    0 讨论(0)
  • 2020-11-28 13:50

    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

    0 讨论(0)
  • 2020-11-28 13:50

    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
    }
    
    0 讨论(0)
提交回复
热议问题