From 587206bd4a799ef0fbc8c79e44acbf94608a1836 Mon Sep 17 00:00:00 2001 From: Plastic-time <247457381+Plastic-time@users.noreply.github.com> Date: Tue, 20 Oct 2026 14:34:48 -0400 Subject: [PATCH] fix: preserve object iteration order in filter results --- lib/internal/filter.js | 4 +++- test/filter.js | 31 +++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/lib/internal/filter.js b/lib/internal/filter.js index a0478059b..4266e5a54 100644 --- a/lib/internal/filter.js +++ b/lib/internal/filter.js @@ -20,7 +20,9 @@ function filterArray(eachfn, arr, iteratee, callback) { function filterGeneric(eachfn, coll, iteratee, callback) { var results = []; - eachfn(coll, (x, index, iterCb) => { + var counter = 0; + eachfn(coll, (x, key, iterCb) => { + const index = counter++; iteratee(x, (err, v) => { if (err) return iterCb(err); if (v) { diff --git a/test/filter.js b/test/filter.js index 5f666e9aa..17a407cb2 100644 --- a/test/filter.js +++ b/test/filter.js @@ -300,3 +300,34 @@ describe("reject", () => { }) }); }); + +// Complete predicates out of order without relying on timer scheduling. +describe('filter object ordering', () => { + ['filter', 'filterLimit', 'reject', 'rejectLimit'].forEach(method => { + [ + {first: 1, second: 2, third: 3}, + {'2.5': 1, '1.5': 2, '0.5': 3} + ].forEach(collection => { + it(`${method} preserves object enumeration order for ${Object.keys(collection)}`, done => { + const callbacks = []; + const iteratee = (value, callback) => { + callbacks.push(() => callback(null, + method.startsWith('reject') ? value === 2 : value !== 2)); + }; + const complete = (err, results) => { + expect(err).to.equal(null); + expect(results).to.eql([1, 3]); + done(); + }; + if (method.endsWith('Limit')) { + async[method](collection, 3, iteratee, complete); + } else { + async[method](collection, iteratee, complete); + } + callbacks[2](); + callbacks[0](); + callbacks[1](); + }); + }); + }); +});