AngularJS: fixed typing of the ng.IPromise.then function

The type definition for ng.IPromise.then was, in my opinion, incorrect
semantically, and breaking on practical examples in my codebase.

An `IPromise`' `then` function takes a callback which is called with the
value of the promise once it's fulfilled. This could be a number, a
string, some object, an array of strings, anything really. Yet the
typing in angular.d.ts specified `then` as taking a `successCallback` of
type `(response: PromiseCallbackArg) => any`.

The definition of `PromiseCallbackArg` seems very permissive at first
glance, as it is defined to be an object with a bunch of fields, all
optional. Any object, a number, and a string all type check correctly
with such a type, but an array of strings, for example, does not
(`cPromise` in the provided list of test).

Furthermore, it seems to me that the `PromiseCallbackArg` definition was
added specifically to support the response type for promises returned by
the Angular `$http` service, the `ng.IHttpPromise`.

So instead of having an incorrect type on the `ng.IPromise.then` function,
I propose we return it to its generic form, and instead override the
type of the inherited `then` function in the `ng.IHttpPromise` interface.
This would also warrant renaming `PromiseCallbackArg` to
`IHttpPromiseCallbackArg`.
This commit is contained in:
Anton Zolotkov
2013-03-20 11:48:15 +09:00
parent 093fb5da49
commit 26d0c8a757
2 changed files with 71 additions and 12 deletions

View File

@@ -58,11 +58,11 @@ angular.module('http-auth-interceptor', [])
.config(['$httpProvider', 'authServiceProvider', <any>function ($httpProvider: ng.IHttpProvider, authServiceProvider) {
var interceptor = ['$rootScope', '$q', <any>function ($rootScope: ng.IScope, $q: ng.IQService) {
function success(response: ng.PromiseCallbackArg) {
function success(response: ng.IHttpPromiseCallbackArg) {
return response;
}
function error(response: ng.PromiseCallbackArg) {
function error(response: ng.IHttpPromiseCallbackArg) {
if (response.status === 401) {
var deferred = $q.defer();
authServiceProvider.pushToBuffer(response.config, deferred);
@@ -79,4 +79,62 @@ angular.module('http-auth-interceptor', [])
}];
$httpProvider.responseInterceptors.push(interceptor);
}]);
}]);
module HttpAndRegularPromiseTests {
interface Person {
firstName: string;
lastName: string;
}
interface ExpectedResponse extends Person {}
interface SomeControllerScope extends ng.IScope {
person: Person;
theAnswer: number;
letters: string[];
}
interface OurApiPromiseCallbackArg extends ng.IHttpPromiseCallbackArg {
data?: ExpectedResponse;
}
var someController: Function = ($scope: SomeControllerScope, $http: ng.IHttpService, $q: ng.IQService) => {
$http.get("http://somewhere/some/resource")
.success((data: ExpectedResponse) => {
$scope.person = data;
});
$http.get("http://somewhere/some/resource")
.then((response: ng.IHttpPromiseCallbackArg) => {
// typing lost, so something like
// var i: number = response.data
// would type check
$scope.person = response.data;
});
$http.get("http://somewhere/some/resource")
.then((response: OurApiPromiseCallbackArg) => {
// typing lost, so something like
// var i: number = response.data
// would NOT type check
$scope.person = response.data;
});
var aPromise: ng.IPromise = $q.when({firstName: "Jack", lastName: "Sparrow"});
aPromise.then((person: Person) => {
$scope.person = person;
});
var bPromise: ng.IPromise = $q.when(42);
bPromise.then((answer: number) => {
$scope.theAnswer = answer;
});
var cPromise: ng.IPromise = $q.when(["a", "b", "c"]);
cPromise.then((letters: string[]) => {
$scope.letters = letters;
});
}
}

View File

@@ -385,15 +385,8 @@ module ng {
when(value: any): IPromise;
}
interface PromiseCallbackArg {
data?: any;
status?: number;
headers?: (headerName: string) => string;
config?: IRequestConfig;
}
interface IPromise {
then(successCallback: (response: PromiseCallbackArg) => any, errorCallback?: (response: PromiseCallbackArg) => any): IPromise;
then(successCallback: (promiseValue: any) => any, errorCallback?: (reason: any) => any): IPromise;
}
interface IDeferred {
@@ -524,9 +517,17 @@ module ng {
transformResponse?: any;
}
interface IHttpPromiseCallbackArg {
data?: any;
status?: number;
headers?: (headerName: string) => string;
config?: IRequestConfig;
}
interface IHttpPromise extends IPromise {
success(callback: (data: any, status: number, headers: (headerName: string) => string, config: IRequestConfig) => any): IHttpPromise;
error(callback: (data: any, status: number, headers: (headerName: string) => string, config: IRequestConfig) => any): IHttpPromise;
then(successCallback: (response: IHttpPromiseCallbackArg) => any, errorCallback?: (response: IHttpPromiseCallbackArg) => any): IPromise;
}
interface IHttpProvider extends IServiceProvider {
@@ -658,4 +659,4 @@ module ng {
}
}
}