Array filter evaluates the immediate truthiness of its callback result. An async callback returns a promise, so resolve the decisions first and then apply a synchronous filter.
Reproduce the behavior safely
Run this self-contained fixture in an empty temporary directory, separate from your project. Shell examples create only named fixture files there. JavaScript examples use local values and send no network requests.
const assert = require('node:assert/strict');
(async () => {
const items = [3, 4, 5];
const allowed = async value => value % 2 === 0;
const wrong = items.filter(allowed);
assert.deepEqual(wrong, items);
const decisions = await Promise.all(items.map(allowed));
const kept = items.filter((_, index) => decisions[index]);
assert.deepEqual(kept, [4]);
console.log(JSON.stringify({decisions, kept}));
})().catch(e => { console.error(e); process.exitCode = 1; });Observed local output
{"decisions":[false,true,false],"kept":[4]}Why the result changes
Promises are truthy even when they eventually fulfill with false. Adding await before the returned array does not change the callbacks into synchronous booleans. The two-phase example preserves index alignment by resolving one decision per original element before filtering that same snapshot. It also puts predicate failures in an aggregate that the caller observes.
Choose and verify the correction
For a small dense array, collect decisions and filter as shown. Preserve the input snapshot so mutations cannot associate results with different records. If one decision fails, decide whether the operation should reject or retain a per-item error for review; do not automatically treat infrastructure errors as permission to keep an item. Use a sequential loop or bounded worker queue when predicates have rate limits or side effects.
Limits and next checks
This arithmetic fixture performs no remote requests and makes no claim about throughput. Promise.all starts no new limit: mapping already called every predicate. Sparse arrays and concurrent mutation need explicit handling beyond this dense example. The related map article explains promise aggregation; the distinct failure here is that filter interprets promise objects as keep decisions.
Related: Awaiting an Array Map Does Not Await Its Tasks: Aggregate the Returned Promises.
Isolated fixture executed on macOS on November 10, 2026 (KST), with v26.5.0. The observed output verifies this example, not every application or runtime.