From eabc8ca7627c57a7de6ce798c3f1671c4535fe6c Mon Sep 17 00:00:00 2001 From: Su-Shing Chen Date: Thu, 19 Apr 2018 07:45:15 +1200 Subject: [PATCH] [@types/when] Make when.js promises compatible with native promises (#24904) * Make when.js promises compatible with native promises * Stricter compiler options and fix revealed issues - Remove optional parameters from callbacks - Replace `Number` type with `number` - Improve type checking of when.settle and promise.inspect * Add stricter types and backwards compatibility * Minor renaming --- types/when/index.d.ts | 70 ++++++++++++++++++++++++++++------------ types/when/tsconfig.json | 6 ++-- types/when/when-tests.ts | 70 ++++++++++++++++++++++++++++++++++++---- 3 files changed, 115 insertions(+), 31 deletions(-) diff --git a/types/when/index.d.ts b/types/when/index.d.ts index a459e031a1..3736cfa7dd 100644 --- a/types/when/index.d.ts +++ b/types/when/index.d.ts @@ -109,7 +109,7 @@ declare namespace When { * @returns a promise that will fulfill with an array of mapped values * or reject if any input promise rejects. */ - function map(promisesOrValues: any[], mapFunc: (value: any, index?: Number) => any): Promise; + function map(promisesOrValues: any[], mapFunc: (value: any, index: number) => any): Promise; /** * Traditional reduce function, similar to `Array.prototype.reduce()`, but @@ -118,10 +118,10 @@ declare namespace When { * be a promise for the starting value. * @param promisesOrValues array or promise for an array of anything, * may contain a mix of promises and values. - * @param reduceFunc function(accumulated:*, x:*, index:Number):*} f reduce function + * @param reduceFunc function(accumulated:*, x:*, index:number):*} f reduce function * @returns a promise that will resolve to the final reduced value */ - function reduce(promisesOrValues: any[], reduceFunc: (reduction: T, value: any, index?: Number) => T | Promise, initialValue: T): Promise; + function reduce(promisesOrValues: any[], reduceFunc: (reduction: T, value: any, index: number) => T | Promise, initialValue: T): Promise; /** * Traditional reduce function, similar to `Array.prototype.reduceRight()`, but @@ -130,22 +130,38 @@ declare namespace When { * be a promise for the starting value. * @param promisesOrValues array or promise for an array of anything, * may contain a mix of promises and values. - * @param reduceFunc function(accumulated:*, x:*, index:Number):*} f reduce function + * @param reduceFunc function(accumulated:*, x:*, index:number):*} f reduce function * @returns a promise that will resolve to the final reduced value */ - function reduceRight(promisesOrValues: any[], reduceFunc: (reduction: T, value: any, index?: Number) => T | Promise, initialValue: T): Promise; + function reduceRight(promisesOrValues: any[], reduceFunc: (reduction: T, value: any, index: number) => T | Promise, initialValue: T): Promise; /** - * Describes the status of a promise. + * Describes the outcome of a promise. * state may be one of: * "fulfilled" - the promise has resolved - * "pending" - the promise is still pending to resolve/reject * "rejected" - the promise has rejected */ - interface Descriptor { - state: string; - value?: T; - reason?: any; + type Descriptor = FulfilledDescriptor | RejectedDescriptor; + + /** + * Snapshot which describes the status of a promise. + * state may be one of: + * "fulfilled" - the promise has resolved + * "rejected" - the promise has rejected + * "pending" - the promise is still pending to resolve/reject + */ + type Snapshot = FulfilledDescriptor | RejectedDescriptor | PendingDescriptor; + + interface FulfilledDescriptor { + state: 'fulfilled'; + value: T; + } + interface RejectedDescriptor { + state: 'rejected'; + reason: any; + } + interface PendingDescriptor { + state: 'pending'; } /** @@ -269,7 +285,26 @@ declare namespace When { // be a constructor with prototype set to an instance of Error. otherwise(exceptionType: any, onRejected?: (reason: any) => U | Promise): Promise; - then(onFulfilled: (value: T) => U | Promise, onRejected?: (reason: any) => U | Promise, onProgress?: (update: any) => void): Promise; + then( + onFulfilled?: ((value: T) => T | Thenable) | undefined | null, + onRejected?: ((reason: any) => T | Thenable) | undefined | null, + onProgress?: (update: any) => void + ): Promise; + then( + onFulfilled: ((value: T) => TResult | Thenable), + onRejected?: ((reason: any) => TResult | Thenable) | undefined | null, + onProgress?: (update: any) => void + ): Promise; + then( + onFulfilled: ((value: T) => T | Thenable) | undefined | null, + onRejected: ((reason: any) => TResult | Thenable), + onProgress?: (update: any) => void + ): Promise; + then( + onFulfilled: ((value: T) => TResult1 | Thenable), + onRejected: ((reason: any) => TResult2 | Thenable), + onProgress?: (update: any) => void + ): Promise; spread(onFulfilled: _.Fn0 | T>): Promise; spread(onFulfilled: _.Fn1 | T>): Promise; @@ -284,13 +319,7 @@ declare namespace When { } interface Thenable { - then(onFulfilled: (value: T) => U, onRejected?: (reason: any) => U): Thenable; - } - - interface Snapshot { - state: string; - value?: T; - reason?: any; + then(onFulfilled?: (value: T) => U, onRejected?: (reason: any) => U): Thenable; } } @@ -369,8 +398,7 @@ declare module "when/node" { interface Resolver { reject(reason: any): void; - resolve(value?: T): void; - resolve(value?: when.Promise): void; + resolve(value?: T | when.Promise): void; } function createCallback(resolver: Resolver): (err: any, arg: TArg) => void; diff --git a/types/when/tsconfig.json b/types/when/tsconfig.json index f903b8862c..2162d713fc 100644 --- a/types/when/tsconfig.json +++ b/types/when/tsconfig.json @@ -6,8 +6,8 @@ ], "noImplicitAny": true, "noImplicitThis": true, - "strictNullChecks": false, - "strictFunctionTypes": false, + "strictNullChecks": true, + "strictFunctionTypes": true, "baseUrl": "../", "typeRoots": [ "../" @@ -20,4 +20,4 @@ "index.d.ts", "when-tests.ts" ] -} \ No newline at end of file +} diff --git a/types/when/when-tests.ts b/types/when/when-tests.ts index 63fbba2234..d44752b950 100644 --- a/types/when/when-tests.ts +++ b/types/when/when-tests.ts @@ -10,13 +10,17 @@ class ForeignPromise { constructor(private readonly value: T) { } - then(onFulfilled: (value: T) => U, onRejected?: (reason: any) => U) { return new ForeignPromise(onFulfilled(this.value)); } + then(onFulfilled?: (value: T) => U, onRejected?: (reason: any) => U): ForeignPromise + then(onFulfilled?: (value: T) => T, onRejected?: (reason: any) => T): ForeignPromise { + return new ForeignPromise(onFulfilled ? onFulfilled(this.value) : this.value); + } }; var promise: when.Promise; var foreign = new ForeignPromise(1); var error = new Error("boom!"); var example: () => void; +var native: Promise; /* * * * * * * * Core * @@ -100,8 +104,16 @@ when.all([when(1), when(2), when(3)]).then(results => { when.map([when(1), 2, 3], (num: number, index: number) => num * index).then((results) => { return results.reduce((r, x) => r + x, 0); }); +when.map([when(1), 2, 3], (num: number) => num * num).then((results) => { + return results.reduce((r, x) => r + x, 0); +}); /* when.reduce(arr, reduceFunc, initialValue) */ +when.reduce([when(1), 2, 3], (reduction: number, value: number, index: number) => { + return reduction += value * index; +}, 0).then((result: number) => { + return result; +}); when.reduce([when(1), 2, 3], (reduction: number, value: number) => { return reduction += value; }, 0).then((result: number) => { @@ -109,6 +121,12 @@ when.reduce([when(1), 2, 3], (reduction: number, value: number) => { }); /* when.reduceRight(arr, reduceFunc, initialValue) */ +when.reduceRight([when(1), 2, 3], (reduction: number, value: number, index: number) => { + return when(value * index) + .then((v) => reduction += v); +}, 0).then((result: number) => { + return result; +}); when.reduceRight([when(1), 2, 3], (reduction: number, value: number) => { return when(value) .then((v) => reduction += v); @@ -118,7 +136,24 @@ when.reduceRight([when(1), 2, 3], (reduction: number, value: number) => /* when.settle(arr) */ when.settle([when(1), when(2), when.reject(new Error("Foo"))]).then(descriptors => { - return descriptors.filter(d => d.state === 'rejected').reduce((r, d) => r + d.value, 0); + return descriptors.reduce((r, d) => { + if (d.state === 'fulfilled') { + return r + d.value; + } else { + console.error(d.reason); + return r; + } + }, 0); +}); +when.settle([when(1), when(2), when.reject(new Error("Foo"))]).then(descriptors => { + return descriptors.reduce((r, d) => { + if (d.state === 'rejected') { + console.error(d.reason); + return r; + } else { + return r + d.value; + } + }, 0); }); /* when.iterate(f, predicate, handler, seed) */ @@ -174,13 +209,16 @@ deferred.reject(error); when(1).done(); when(1).done((val: number) => console.log(val)); +when(1).done(undefined, (err: any) => console.log(err)); when(1).done((val: number) => console.log(val), (err: any) => console.log(err)); /* promise.then(onFulfilled) */ +promise = when(1).then(); promise = when(1).then((val: number) => val + val); promise = when(1).then((val: number) => when(val + val)); +promise = when(1).then(undefined, (err: any) => 2); promise = when(1).then((val: number) => val + val, (err: any) => 2); promise = when(1).then((val: number) => when(val + val), (err: any) => 2); @@ -261,6 +299,17 @@ var status: { status = when(1).inspect() +var status2: when.Snapshot; + +status2 = when(1).inspect(); +if (status2.state === 'fulfilled') { + console.log(status2.value + 2); +} else if (status2.state === 'rejected') { + console.log(status2.reason); +} else { + console.log(status2.state === 'pending'); +} + /* promise.with(thisArg) */ promise = when(1).with(2); @@ -416,8 +465,8 @@ example = function () { /* node.liftCallback */ example = function () { - var fetchData: (key: string) => when.Promise; - var handleData: (err: any, result: number) => void; + var fetchData: (key: string) => when.Promise = () => when(1); + var handleData: (err: any, result: number) => void = () => undefined; var handlePromisedData: (result: when.Promise) => when.Promise; handlePromisedData = nodefn.liftCallback(handleData); @@ -428,8 +477,8 @@ example = function () { /* node.bindCallback */ example = function () { - var fetchData: (key: string) => when.Promise; - var handleData: (err: any, result: number) => void; + var fetchData: (key: string) => when.Promise = () => when(1); + var handleData: (err: any, result: number) => void = () => undefined; nodefn.bindCallback(fetchData('thing'), handleData); }; @@ -437,9 +486,16 @@ example = function () { /* node.createCallback */ example = function () { - when.promise((resolve, reject) => + when.promise((resolve, reject) => nodeFn2(1, '2', nodefn.createCallback({ resolve: resolve, reject: reject }))) .then( (value: number) => console.log(value), (err: any) => console.error(err)); }; + +/* * * * * * * * * * * + * Native Promises * + * * * * * * * * * * */ + +native = Promise.resolve(when(1)); +native = Promise.all([when(1)]).then(([x]) => x);