Skip to content

Commit

Permalink
New: Implement Pledge.any()
Browse files Browse the repository at this point in the history
  • Loading branch information
nzakas committed Oct 29, 2020
1 parent dd6014b commit 8bb305d
Show file tree
Hide file tree
Showing 3 changed files with 198 additions and 1 deletion.
97 changes: 96 additions & 1 deletion src/pledge.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

import { PledgeSymbol } from "./pledge-symbol.js";
import { PledgeReactionJob, hostEnqueuePledgeJob } from "./pledge-jobs.js";
import { isObject, isCallable, isConstructor } from "./utilities.js";
import { isObject, isCallable, isConstructor, PledgeAggregateError } from "./utilities.js";
import {
isPledge,
createResolvingFunctions,
Expand Down Expand Up @@ -86,6 +86,22 @@ export class Pledge {
return this;
}

static any(iterable) {

const C = this;
const pledgeCapability = new PledgeCapability(C);

try {
const pledgeResolve = getPledgeResolve(C);
const iteratorRecord = iterable[Symbol.iterator]();
const result = performPledgeAny(iteratorRecord, C, pledgeCapability, pledgeResolve);
return result;
} catch (error) {
pledgeCapability.reject(error);
return error;
}
}

static race(iterable) {

const C = this;
Expand Down Expand Up @@ -267,6 +283,85 @@ function getPledgeResolve(pledgeConstructor) {
return pledgeResolve;
}

//-----------------------------------------------------------------------------
// 26.6.4.3.1 PerformPromiseAny ( iteratorRecord, constructor,
// resultCapability, promiseResolve )
//-----------------------------------------------------------------------------

function performPledgeAny(iteratorRecord, constructor, resultCapability, pledgeResolve) {

assertIsConstructor(constructor);
assertIsCallable(pledgeResolve);

const errors = [];
const remainingElementsCount = { value: 1 };
let index = 0;

for (const nextValue of iteratorRecord) {

errors.push(undefined);

const nextPledge = pledgeResolve(constructor, nextValue);
const rejectElement = createPledgeAnyRejectElement(index, errors, resultCapability, remainingElementsCount);

remainingElementsCount.value = remainingElementsCount.value + 1;
nextPledge.then(resultCapability.resolve, rejectElement);
index = index + 1;
}

remainingElementsCount.value = remainingElementsCount.value - 1;
if (remainingElementsCount.value === 0) {
const error = new PledgeAggregateError();
Object.defineProperty(error, "errors", {
configurable: true,
enumerable: false,
writable: true,
value: errors
});

resultCapability.reject(error);
}

return resultCapability.pledge;
}

//-----------------------------------------------------------------------------
// 26.6.4.3.2 Promise.any Reject Element Functions
//-----------------------------------------------------------------------------

// Note: this function doesn't exist in the spec, I've added it for clarity

function createPledgeAnyRejectElement(index, errors, pledgeCapability, remainingElementsCount) {

const alreadyCalled = { value: false };

return x => {

if (alreadyCalled.value) {
return;
}

alreadyCalled.value = true;

errors[index] = x;
remainingElementsCount.value = remainingElementsCount.value - 1;

if (remainingElementsCount.value === 0) {
const error = new PledgeAggregateError();
Object.defineProperty(error, "errors", {
configurable: true,
enumerable: false,
writable: true,
value: errors
});

return pledgeCapability.reject(error);

}

};
}

//-----------------------------------------------------------------------------
// 26.6.4.5.1 PerformPromiseRace ( iteratorRecord, constructor,
// resultCapability, promiseResolve )
Expand Down
33 changes: 33 additions & 0 deletions src/utilities.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,36 @@ export function isCallable(argument) {
export function isConstructor(argument) {
return typeof argument === "function" && typeof argument.prototype !== "undefined";
}


//-----------------------------------------------------------------------------
// 19.5.7 AggregateError Objects
//-----------------------------------------------------------------------------

export function PledgeAggregateError(errors=[], message) {

const O = new.target === undefined ? new PledgeAggregateError() : this;

if (typeof message !== "undefined") {
const msg = String(message);

Object.defineProperty(O, "message", {
value: msg,
writable: true,
enumerable: false,
configurable: true
});
}

// errors can be an iterable
const errorsList = [...errors];

Object.defineProperty(O, "errors", {
configurable: true,
enumerable: false,
writable: true,
value: errorsList
});

return O;
}
69 changes: 69 additions & 0 deletions tests/pledge.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,75 @@ describe("Pledge", () => {
});
});

describe("Pledge.any()", () => {

it("should throw an error when `this` is not a constructor", () => {
expect(() => {
Pledge.any.call({}, []);
}).to.throw(/constructor/);
});

it("should return the first value that was resolved", done => {

const pledge = Pledge.any([
Pledge.resolve(42),
Pledge.resolve(43),
Pledge.resolve(44)
]);

pledge.then(value => {
expect(value).to.equal(42);
done();
});

});

it("should return the second pledge resolution when it is resolved first", done => {

const pledge = Pledge.any([
Pledge.reject(42),
Pledge.resolve(43),
Pledge.resolve(44)
]);

pledge.then(value => {
expect(value).to.equal(43);
done();
});

});

it("should return an aggregate error when all pledges are rejected", done => {

const pledge = Pledge.any([
Pledge.reject(42),
Pledge.reject(43),
Pledge.reject(44)
]);

pledge.catch(reason => {
expect(reason.errors).to.deep.equal([42, 43, 44]);
done();
});

});

it("should return the third pledge value when it is resolved first", done => {

const pledge = Pledge.any([
delayResolvePledge(42, 500),
Pledge.reject(43),
Pledge.resolve(44)
]);

pledge.then(value => {
expect(value).to.equal(44);
done();
});

});
});




Expand Down

0 comments on commit 8bb305d

Please sign in to comment.