使用返回Promise的函数过滤数组

IT技术 javascript arrays ecmascript-6 es6-promise
2021-01-15 19:54:51

给定的

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
 });

长度为 3,因为返回的是 Promises,而不是值。有没有办法用返回 Promise 的函数过滤数组?

注意:对于这个例子,fs.stat 已经被 setTimeout 替换,参见https://github.com/silenceisgolden/learn-esnext/blob/array-filter-async-function/tutorials/array-filter-with-async- function.js为具体代码。

6个回答

这是 2017 年使用 async/await 的优雅解决方案:

非常直接的用法:

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

辅助函数(将其复制到您的网页中):

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)
}

演示:

// 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)
}

我什至会投入一个CodePen

这与普通 Array.filter 的行为有细微的不同:如果您尝试以包含未定义元素的方式过滤数组,您将丢失它们。例如,它应该filter([1, 2, undefined, 3], (x) => x !== 1)返回[2, 3],而不是返回[2, undefined, 3]
2021-03-16 19:54:51
可能这个函数应该重命名为不同的东西,比如filterAsync. 我承认,在我草率的 SO-and-paste 工作流程中,只阅读了您答案的第一段,看到您使用了filter(),并假设Array.filter将支持异步回调......🙃
2021-03-16 19:54:51
@Tamlyn 添加了符号哨兵来解决undefined情况:)
2021-03-17 19:54:51
@TimPerry 正确,请随时修改答案,以便更有意义:)
2021-03-27 19:54:51
一种选择是返回一个Symbol哨兵值而不是undefined.
2021-04-12 19:54:51

正如评论中提到的,Array.prototype.filter同步的,因此不支持 Promises。

由于您现在(理论上)可以使用 ES6 对内置类型进行子类化,因此您应该能够添加自己的异步方法来包装现有的过滤器函数:

注意:我已经注释掉了子类化,因为 Babel 目前还不支持 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 演示

你是对的,我只是警告那些盲目复制粘贴已接受答案的人:)
2021-03-31 19:54:51
@FarzadYZ 好点,看起来你可以取消对该块的注释并让它工作......
2021-04-05 19:54:51
@FarzadYZ 子类的实现只是一个例子。您不需要具有真正子类化的构造函数,因为您将使用基本 Array 构造函数而不是使用您自己的数据存储
2021-04-07 19:54:51
这是无效的,因为super()必须在对thisinside进行任何赋值之前调用constructor
2021-04-12 19:54:51

这里有一个方法:

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));

filterAsync函数采用一个数组和一个函数,该函数必须返回truefalse返回一个解析为trueor的Promise,false您所要求的(几乎,我没有重载Promise拒绝,因为我认为这是一个坏主意)。如果您对此有任何疑问,请告诉我。

对于typescript民间(或 es6 只是删除类型语法)

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

编辑:演示

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

这是一个很好的答案。我所做的唯一调整是添加readonly到数组参数类型。
2021-03-29 19:54:51
需要演示如何调用它
2021-04-08 19:54:51

Promise Reducer 来拯救你!

[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));

减速机很棒。“将我的问题减少到我的目标”对于任何比简单工具将为您解决的问题更复杂的事情来说似乎是一个很好的策略,即过滤一系列并非立即可用的事情。