Skip to content

Commit

Permalink
url: improve URLSearchParams creation performance
Browse files Browse the repository at this point in the history
PR-URL: #47190
Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com>
  • Loading branch information
anonrig authored and RafaelGSS committed Apr 8, 2023
1 parent 760e13c commit d243115
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 25 deletions.
70 changes: 45 additions & 25 deletions lib/internal/url.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

const {
Array,
ArrayIsArray,
ArrayPrototypeJoin,
ArrayPrototypeMap,
ArrayPrototypePush,
Expand Down Expand Up @@ -156,52 +157,74 @@ function isURLSearchParams(self) {
}

class URLSearchParams {
[searchParams] = [];

// "associated url object"
[context] = null;

// URL Standard says the default value is '', but as undefined and '' have
// the same result, undefined is used to prevent unnecessary parsing.
// Default parameter is necessary to keep URLSearchParams.length === 0 in
// accordance with Web IDL spec.
constructor(init = undefined) {
if (init === null || init === undefined) {
this[searchParams] = [];
if (init == null) {
// Do nothing
} else if (typeof init === 'object' || typeof init === 'function') {
const method = init[SymbolIterator];
if (method === this[SymbolIterator]) {
// While the spec does not have this branch, we can use it as a
// shortcut to avoid having to go through the costly generic iterator.
const childParams = init[searchParams];
this[searchParams] = childParams.slice();
} else if (method !== null && method !== undefined) {
} else if (method != null) {
// Sequence<sequence<USVString>>
if (typeof method !== 'function') {
throw new ERR_ARG_NOT_ITERABLE('Query pairs');
}

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

this[searchParams] = [];
for (const pair of pairs) {
if (pair.length !== 2) {
throw new ERR_INVALID_TUPLE('Each query pair', '[name, value]');
let length = 0;

for (const element of pair) {
length++;
ArrayPrototypePush(this[searchParams], toUSVString(element));
}

// If innerSequence's size is not 2, then throw a TypeError.
if (length !== 2) {
throw new ERR_INVALID_TUPLE('Each query pair', '[name, value]');
}
}
ArrayPrototypePush(this[searchParams], pair[0], pair[1]);
}
} else {
// Record<USVString, USVString>
// Need to use reflection APIs for full spec compliance.
const visited = {};
this[searchParams] = [];
const keys = ReflectOwnKeys(init);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
Expand All @@ -223,13 +246,10 @@ class URLSearchParams {
}
}
} else {
// USVString
// https://url.spec.whatwg.org/#dom-urlsearchparams-urlsearchparams
init = toUSVString(init);
this[searchParams] = init ? parseParams(init) : [];
}

// "associated url object"
this[context] = null;
}

[inspect.custom](recurseTimes, ctx) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ function makeIterableFunc(array) {
assert.throws(() => new URLSearchParams([null]), tupleError);
assert.throws(() => new URLSearchParams([{ [Symbol.iterator]: 42 }]),
tupleError);

assert.throws(() => new URLSearchParams(
makeIterableFunc([['key', 'val', 'val2']])
), tupleError);
}

{
Expand Down

0 comments on commit d243115

Please sign in to comment.