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

时间:2015-10-26 21:00:29

标签: javascript arrays ecmascript-6 es6-promise

鉴于

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

16 个答案:

答案 0 :(得分:26)

正如评论中所提到的,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 Demo

答案 1 :(得分:25)

这是一种方式:

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或返回一个解析为truefalse的promise,你要求的是什么(差不多,我没有超载承诺拒绝因为我认为这是一个坏主意)。如果您对此有任何疑问,请与我们联系。



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)) };
&#13;
<div id="div"></div>
&#13;
&#13;
&#13;

答案 2 :(得分:23)

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

非常简单的用法:

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

答案 3 :(得分:10)

承诺减速机救援!

$(document).ready(function(){

	
var element = $("#firstshirt"); // global variable
var getCanvas; // global variable
 
    $("#btn-Preview-Image").on('click', function () {
         html2canvas(element, {
         onrendered: function (canvas) {
                $("#previewImage").append(canvas);
                getCanvas = canvas;
             }
         });
    });

	$("#btn-Convert-Html2Image").on('click', function () {
    var imgageData = getCanvas.toDataURL("image/png");
    // Now browser starts downloading it instead of just showing it
    var newData = imgageData.replace(/^torcdesign.com:image\/png/, "data:application/octet-stream");
    $("#btn-Convert-Html2Image").attr("download", "<?php echo $confirm_coded ?>").attr("href", newData);
	});

});

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

答案 4 :(得分:4)

对于打字稿民谣(或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]);
}

答案 5 :(得分:3)

游戏后期但由于没有其他人提及它,Bluebird支持Promise.map,这是我需要aysnc处理条件的过滤器,

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

答案 6 :(得分:2)

两行,完全类型安全

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

答案 7 :(得分:0)

这样做的有效方法(但似乎太乱了):

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

再次,似乎太乱了。

答案 8 :(得分:0)

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

请注意,如果(在当前情况下)您不必担心filter() 需要序列化的副作用,您也可以执行以下操作:

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

答案 9 :(得分:0)

如果有人对现代打字稿解决方案(使用失败符号进行过滤)感兴趣:

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

答案 10 :(得分:0)

asyncFilter方法:

Array.prototype.asyncFilter = async function(f){
    var array = this;
    var booleans = await Promise.all(array.map(f));
    return array.filter((x,i)=>booleans[i])
}

答案 11 :(得分:0)

您可以执行以下操作...

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

包装在异步函数中...


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

答案 12 :(得分:0)

晚了聚会,我知道我的答案与其他已经发布的答案相似,但是我准备共享的功能已经准备好放入任何代码中并可以使用。 和往常一样,当您必须对数组执行复杂的操作时,reduce为王:

const filterAsync = (asyncPred) => arr => 
  arr.reduce(async (acc,item) => {
    const pass = await asyncPred(item);
    if(pass) (await acc).push(item);
    return acc;
  },[]);

它使用现代语法,因此请确保您的目标支持它。要100%正确,您应该使用Promise.resolve([])作为初始值,但是JS不在乎,因此它更短。

然后您可以像这样使用它:

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]

答案 13 :(得分:0)

这是@ pie6k的Typescript版本的较短版本:

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
}

答案 14 :(得分:0)

出于生产目的,您可能希望使用lodasync之类的库:

import { filterAsync } from 'lodasync'

const result = await filterAsync(async(element) => {
  await doSomething()
  return element > 3
}, array)

在后台,它通过在每个元素上调用回调来映射数组,并使用结果过滤数组。但是您不应该重新发明轮子。

答案 15 :(得分:0)

只有一个班轮可以做到这一点。

const filterPromise = (values, fn) => 
    Promise.all(values.map(fn)).then(booleans => values.filter((_, i) => booleans[i]));

将数组传递到values并将函数传递到fn

here中提供了有关此衬板如何工作的更多描述。