Skip to content

Commit

Permalink
lib: implement safe alternatives to Promise static methods
Browse files Browse the repository at this point in the history
PR-URL: #43728
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
  • Loading branch information
aduh95 authored and targos committed Jul 12, 2022
1 parent 428d03c commit dc484b6
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 9 deletions.
11 changes: 4 additions & 7 deletions lib/internal/modules/esm/module_job.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,17 @@

const {
ArrayPrototypeJoin,
ArrayPrototypeMap,
ArrayPrototypePush,
ArrayPrototypeSome,
FunctionPrototype,
ObjectCreate,
ObjectSetPrototypeOf,
PromiseAll,
PromiseResolve,
PromisePrototypeCatch,
ReflectApply,
RegExpPrototypeExec,
RegExpPrototypeSymbolReplace,
SafeArrayIterator,
SafePromiseAll,
SafeSet,
StringPrototypeIncludes,
StringPrototypeSplit,
Expand Down Expand Up @@ -82,9 +80,9 @@ class ModuleJob {
});

if (promises !== undefined)
await PromiseAll(new SafeArrayIterator(promises));
await SafePromiseAll(promises);

return PromiseAll(new SafeArrayIterator(dependencyJobs));
return SafePromiseAll(dependencyJobs);
};
// Promise for the list of all dependencyJobs.
this.linked = link();
Expand Down Expand Up @@ -112,8 +110,7 @@ class ModuleJob {
}
jobsInGraph.add(moduleJob);
const dependencyJobs = await moduleJob.linked;
return PromiseAll(new SafeArrayIterator(
ArrayPrototypeMap(dependencyJobs, addJobsToDependencyGraph)));
return SafePromiseAll(dependencyJobs, addJobsToDependencyGraph);
};
await addJobsToDependencyGraph(this);

Expand Down
59 changes: 59 additions & 0 deletions lib/internal/per_context/primordials.js
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ function copyPrototype(src, dest, prefix) {

const {
ArrayPrototypeForEach,
ArrayPrototypeMap,
FinalizationRegistry,
FunctionPrototypeCall,
Map,
Expand Down Expand Up @@ -434,5 +435,63 @@ primordials.AsyncIteratorPrototype =
primordials.ReflectGetPrototypeOf(
async function* () {}).prototype);

const arrayToSafePromiseIterable = (promises, mapFn) =>
new primordials.SafeArrayIterator(
ArrayPrototypeMap(
promises,
(promise, i) =>
new SafePromise((a, b) => PromisePrototypeThen(mapFn == null ? promise : mapFn(promise, i), a, b))
)
);

/**
* @param {Promise<any>[]} promises
* @param {(v: Promise<any>, k: number) => Promise<any>} [mapFn]
* @returns {Promise<any[]>}
*/
primordials.SafePromiseAll = (promises, mapFn) =>
// Wrapping on a new Promise is necessary to not expose the SafePromise
// prototype to user-land.
new Promise((a, b) =>
SafePromise.all(arrayToSafePromiseIterable(promises, mapFn)).then(a, b)
);

/**
* @param {Promise<any>[]} promises
* @param {(v: Promise<any>, k: number) => Promise<any>} [mapFn]
* @returns {Promise<PromiseSettledResult<any>[]>}
*/
primordials.SafePromiseAllSettled = (promises, mapFn) =>
// Wrapping on a new Promise is necessary to not expose the SafePromise
// prototype to user-land.
new Promise((a, b) =>
SafePromise.allSettled(arrayToSafePromiseIterable(promises, mapFn)).then(a, b)
);

/**
* @param {Promise<any>[]} promises
* @param {(v: Promise<any>, k: number) => Promise<any>} [mapFn]
* @returns {Promise<any>}
*/
primordials.SafePromiseAny = (promises, mapFn) =>
// Wrapping on a new Promise is necessary to not expose the SafePromise
// prototype to user-land.
new Promise((a, b) =>
SafePromise.any(arrayToSafePromiseIterable(promises, mapFn)).then(a, b)
);

/**
* @param {Promise<any>[]} promises
* @param {(v: Promise<any>, k: number) => Promise<any>} [mapFn]
* @returns {Promise<any>}
*/
primordials.SafePromiseRace = (promises, mapFn) =>
// Wrapping on a new Promise is necessary to not expose the SafePromise
// prototype to user-land.
new Promise((a, b) =>
SafePromise.race(arrayToSafePromiseIterable(promises, mapFn)).then(a, b)
);


ObjectSetPrototypeOf(primordials, null);
ObjectFreeze(primordials);
4 changes: 2 additions & 2 deletions lib/internal/vm/module.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ const {
ObjectDefineProperty,
ObjectGetPrototypeOf,
ObjectSetPrototypeOf,
PromiseAll,
ReflectApply,
SafePromiseAll,
SafeWeakMap,
Symbol,
SymbolToStringTag,
Expand Down Expand Up @@ -330,7 +330,7 @@ class SourceTextModule extends Module {

try {
if (promises !== undefined) {
await PromiseAll(promises);
await SafePromiseAll(promises);
}
} catch (e) {
this.#error = e;
Expand Down
14 changes: 14 additions & 0 deletions test/parallel/test-primordials-promise.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,18 @@ const assert = require('assert');
const {
PromisePrototypeCatch,
PromisePrototypeThen,
SafePromiseAll,
SafePromiseAllSettled,
SafePromiseAny,
SafePromisePrototypeFinally,
SafePromiseRace,
} = require('internal/test/binding').primordials;

Array.prototype[Symbol.iterator] = common.mustNotCall();
Promise.all = common.mustNotCall();
Promise.allSettled = common.mustNotCall();
Promise.any = common.mustNotCall();
Promise.race = common.mustNotCall();
Promise.prototype.catch = common.mustNotCall();
Promise.prototype.finally = common.mustNotCall();
Promise.prototype.then = common.mustNotCall();
Expand All @@ -18,6 +27,11 @@ assertIsPromise(PromisePrototypeCatch(Promise.reject(), common.mustCall()));
assertIsPromise(PromisePrototypeThen(test(), common.mustCall()));
assertIsPromise(SafePromisePrototypeFinally(test(), common.mustCall()));

assertIsPromise(SafePromiseAll([test()]));
assertIsPromise(SafePromiseAllSettled([test()]));
assertIsPromise(SafePromiseAny([test()]));
assertIsPromise(SafePromiseRace([test()]));

async function test() {
const catchFn = common.mustCall();
const finallyFn = common.mustCall();
Expand Down

0 comments on commit dc484b6

Please sign in to comment.