Skip to content

Commit b2cd992

Browse files
committed
url: improve URLSearchParams creation performance
1 parent 545b594 commit b2cd992

File tree

2 files changed

+49
-25
lines changed

2 files changed

+49
-25
lines changed

lib/internal/url.js

+45-25
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
const {
44
Array,
5+
ArrayIsArray,
56
ArrayPrototypeJoin,
67
ArrayPrototypeMap,
78
ArrayPrototypePush,
@@ -151,52 +152,74 @@ function isURLSearchParams(self) {
151152
}
152153

153154
class URLSearchParams {
155+
[searchParams] = [];
156+
157+
// "associated url object"
158+
[context] = null;
159+
154160
// URL Standard says the default value is '', but as undefined and '' have
155161
// the same result, undefined is used to prevent unnecessary parsing.
156162
// Default parameter is necessary to keep URLSearchParams.length === 0 in
157163
// accordance with Web IDL spec.
158164
constructor(init = undefined) {
159-
if (init === null || init === undefined) {
160-
this[searchParams] = [];
165+
if (init == null) {
166+
// Do nothing
161167
} else if (typeof init === 'object' || typeof init === 'function') {
162168
const method = init[SymbolIterator];
163169
if (method === this[SymbolIterator]) {
164170
// While the spec does not have this branch, we can use it as a
165171
// shortcut to avoid having to go through the costly generic iterator.
166172
const childParams = init[searchParams];
167173
this[searchParams] = childParams.slice();
168-
} else if (method !== null && method !== undefined) {
174+
} else if (method != null) {
175+
// Sequence<sequence<USVString>>
169176
if (typeof method !== 'function') {
170177
throw new ERR_ARG_NOT_ITERABLE('Query pairs');
171178
}
172179

173-
// Sequence<sequence<USVString>>
174-
// Note: per spec we have to first exhaust the lists then process them
175-
const pairs = [];
180+
// The following implementationd differs from the URL specification:
181+
// Sequences must first be converted from ECMAScript objects before
182+
// and operations are done on them, and the operation of converting
183+
// the sequences would first exhaust the iterators. If the iterator
184+
// returns something invalid in the middle, whether it would be called
185+
// after that would be an observable change to the users.
186+
// Exhausting the iterator and later converting them to USVString comes
187+
// with a significant cost (~40-80%). In order optimize URLSearchParams
188+
// creation duration, Node.js merges the iteration and converting
189+
// iterations into a single iteration.
176190
for (const pair of init) {
177-
if ((typeof pair !== 'object' && typeof pair !== 'function') ||
178-
pair === null ||
179-
typeof pair[SymbolIterator] !== 'function') {
191+
if (pair == null) {
180192
throw new ERR_INVALID_TUPLE('Each query pair', '[name, value]');
181-
}
182-
const convertedPair = [];
183-
for (const element of pair)
184-
ArrayPrototypePush(convertedPair, toUSVString(element));
185-
ArrayPrototypePush(pairs, convertedPair);
186-
}
193+
} else if (ArrayIsArray(pair)) {
194+
// If innerSequence's size is not 2, then throw a TypeError.
195+
if (pair.length !== 2) {
196+
throw new ERR_INVALID_TUPLE('Each query pair', '[name, value]');
197+
}
198+
// Append (innerSequence[0], innerSequence[1]) to querys list.
199+
ArrayPrototypePush(this[searchParams], toUSVString(pair[0]), toUSVString(pair[1]));
200+
} else {
201+
if (((typeof pair !== 'object' && typeof pair !== 'function') ||
202+
typeof pair[SymbolIterator] !== 'function')) {
203+
throw new ERR_INVALID_TUPLE('Each query pair', '[name, value]');
204+
}
187205

188-
this[searchParams] = [];
189-
for (const pair of pairs) {
190-
if (pair.length !== 2) {
191-
throw new ERR_INVALID_TUPLE('Each query pair', '[name, value]');
206+
let length = 0;
207+
208+
for (const element of pair) {
209+
length++;
210+
ArrayPrototypePush(this[searchParams], toUSVString(element));
211+
}
212+
213+
// If innerSequence's size is not 2, then throw a TypeError.
214+
if (length !== 2) {
215+
throw new ERR_INVALID_TUPLE('Each query pair', '[name, value]');
216+
}
192217
}
193-
ArrayPrototypePush(this[searchParams], pair[0], pair[1]);
194218
}
195219
} else {
196220
// Record<USVString, USVString>
197221
// Need to use reflection APIs for full spec compliance.
198222
const visited = {};
199-
this[searchParams] = [];
200223
const keys = ReflectOwnKeys(init);
201224
for (let i = 0; i < keys.length; i++) {
202225
const key = keys[i];
@@ -218,13 +241,10 @@ class URLSearchParams {
218241
}
219242
}
220243
} else {
221-
// USVString
244+
// https://url.spec.whatwg.org/#dom-urlsearchparams-urlsearchparams
222245
init = toUSVString(init);
223246
this[searchParams] = init ? parseParams(init) : [];
224247
}
225-
226-
// "associated url object"
227-
this[context] = null;
228248
}
229249

230250
[inspect.custom](recurseTimes, ctx) {

test/parallel/test-whatwg-url-custom-searchparams-constructor.js

+4
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ function makeIterableFunc(array) {
4747
assert.throws(() => new URLSearchParams([null]), tupleError);
4848
assert.throws(() => new URLSearchParams([{ [Symbol.iterator]: 42 }]),
4949
tupleError);
50+
51+
assert.throws(() => new URLSearchParams(
52+
makeIterableFunc([['key', 'val', 'val2']])
53+
), tupleError);
5054
}
5155

5256
{

0 commit comments

Comments
 (0)