web-dev-qa-db-ja.com

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

値ではなくPromiseが返されるため、長さは3です。 Promiseを返す関数で配列をフィルターする方法はありますか?

注:この例では、fs.statはsetTimeoutに置き換えられています。 https://github.com/silenceisgolden/learn-esnext/blob/array-filter-async-function/tutorials/array-filter- with-async-function.js 特定のコード用。

42
ajklein

コメントで述べたように、Array.prototype.filterは同期的であるため、Promiseをサポートしません。

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

28
CodingIntrigue

方法は次のとおりです。

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関数は、配列と、trueまたはfalseを返すか、trueまたはfalseに解決されるプロミスを返す必要がある関数を受け取ります。 (ほとんど、私はそれが悪い考えだと思うので、私は約束拒否をオーバーロードしませんでした)。それについて質問があれば教えてください。

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>
27
jib

Async/awaitを使用した2017年のエレガントなソリューションを次に示します。

非常に簡単な使用法:

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

ヘルパー関数(これを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)
}

デモ:

// 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 をスローします。

26
Gabe Rogan

レデューサーへの約束の減速機!

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

減速機は素晴らしいです。 「私の問題を目標に合わせる」は、単純なツールがあなたのために解決するものよりも複雑なもの、つまりすぐに利用できないものの配列をフィルタリングするためのかなり良い戦略のようです。

10
Dan Ross

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

ゲームに遅れたが、他に誰も言及していないので、BluebirdはPromise.mapをサポートしています。

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

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

誰かが最新のTypeScriptソリューションに興味がある場合(失敗シンボルがフィルタリングに使用されている場合):

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

このようなことができます...

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;
}
0
sudo soul

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

これを行うための有効な方法(ただし、面倒すぎるようです):

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

繰り返しますが、あまりにも面倒です。

0
ajklein