Use sequentially promise to run some ajax requests in Javascript and jQuery - javascript

I have steps array like:
var stepsToDo = [step1, step2, step3,...]
every step does ajax call:
function step1() { return $.ajax({ ... }); }
function step2() { return $.ajax({ ... }); }
The sample ajax call for step 1 (step 2 and others are similar):
return $.ajax({
url: "test.php",
type: "POST",
dataType: "json",
data: {"step-1": ""},
beforeSend: function () {
writeResult("Processing-> test 1");
},
success: function (result) {
if (result) {
if (result instanceof Array) {
writeResult("Found: " + result.length + " items");
arr = result;
} else {
writeResult(result);
arr = [];
}
}
}
});
function writeResult(str) { console.log(str); }
I want to execute sequentially (defined in stepsToDo).
I tried like:
stepsToDo.reduce(
(promise, method) => {
return promise.then(method);
},
Promise.resolve()
);
Nothing happens, no print in console happens.
Why ?

Drop the new Promise. Your steps are functions that return promises, not promise executors - they never would call a resolve callback passed into them.

For the reducer function you initially created...
stepsToDo.reduce((promise, method) => {
return promise.then(_ => new Promise(method));
}, Promise.resolve());
...your step functions should resolve or reject the promises created in your reducer - therefore should implement the promise executor signature, I.E:
type executor = (resolve: (value?: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void;
For example:
function step1(resolve, reject) {
$
.ajax({ /* ... */ })
.done(resolve)
.fail(reject);
}
EDIT
As #Bergi states in the comments, this is an anti-pattern. Instead you could move the Promise construction into your step functions. A basic example (the jQuery ajax API still has to be converted to promises)
function step1() {
return new Promise((resolve, reject) => {
$.ajax({ /* ... */ }).done(resolve).fail(reject);
});
}
If all your steps return you promises your reducer can get much simpler:
stepsToDo.reduce((promise, method) => promise.then(method), Promise.resolve());
You could in this case even just return the result of $.ajax, since jQuery promises do implement a then method:
function step1() {
return $.ajax({ /* ... */ });
}
If a native promise is required for error handling, i.e. Promise.catch, you can explicitly cast the jQuery promise to a native promise with Promise.resolve:
function step1() {
return Promise.resolve($.ajax({ /* ... */ }));
}

I'm not sure what's wrong with the other answers but $.get (or ajax or post) will return a promise like.
So your step methods can look like this:
var stepOne = () => {
return $.ajax({//just return the promise like
url: "test.php",
type: "POST",
dataType: "json",
data: {"step-1": ""}
});
}
You can then reduce it to one promise that resolves with the results of the three steps:
[stepOne,stepTwo,stepThree].reduce(
(p,fn)=>
p.then(
results=>fn().then(result=>results.concat([result]))
),
$.Deferred().resolve([])
)
.then(
results=>console.log("got results:",results),
err=>console.warn("Something went wrong:",err)
);
You see I don't pass Promise.resolve as second argument to reduce but $.Deferred().resolve([]), this is jQuery promise like value. You can now support browsers that don't have native promises without the need to polyfill.
Although if you need to support those I'd recomment not using the arrow function syntax and use function(something){return something} instead of something=>something

There most be something missing from the original question or the implementation. If all the step functions return the$.ajax promise then the Array.reduce pattern should work.
Below is a working prove of concept using a step() function to simulate the asynchronous code execution and using the exact same Array.reduce pattern:
// Step generator
function step(number) {
return function() {
return $.Deferred(function(def) {
setTimeout(function() {
console.log('Running Step ', number);
def.resolve();
}, Math.floor(Math.random() * Math.floor(250)));
}).promise();
}
}
// List of steps
var stepsToDo = [step(1), step(2), step(3), step(4), step(5)];
// Consume steps one at a time
stepsToDo.reduce(
(promise, method) => {
return promise.then(method);
},
Promise.resolve()
).then(() => console.log('All Done!'));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

Related

Cancel pending API calls in Restangular

I have API service:
var SearchSuggestionApi = function (Restangular) {
return {
getSuggestion: function (keyword) {
return Restangular.one('search').customGET(null, {keyword:keyword});
}
};
};
SearchSuggestionApi.$inject = [
'Restangular'
];
I have controller to call this API:
vm.getSuggestion = function (keyword) {
SearchSuggestionApi.getSuggestion(keyword)
.then(
function (data) {
vm.listData = data;
}, function (error) {
console.log(error);
});
};
My problem is when I call vm.getSuggestion(keyword) two or many time (must call than one time). Such as:
vm.getSuggestion('a'); // => Return a array with many object in this
vm.getSuggestion('a b');// => Return empty array
Because vm.getSuggestion('a') return many data, it will finish after vm.getSuggestion('a b'). So vm.listData is [{object1}, {object2}, ...], but I want to vm.listData is [] (response data of the last function).
How can to cancel pending API call in first function when I call seconds function or another ways to get the last response data and set for vm.listData.
I researched some articles about cancel pending API calls, but it not help me about my problem.
Thanks for your help :)
There are various ways of solving this:
You can simply check in your then callback whether the value received is still current:
vm.getSuggestion = function (keyword) {
SearchSuggestionApi.getSuggestion(keyword)
.then(
function (data) {
if (vm.keyword === keyword) {
vm.listData = data;
}
}, function (error) {
console.log(error);
});
};
You can cancel the request by specifying a timeout promise
If you are solving this problem often, you might wish to replace the promise by an RxJS observable stream with the appropriate operators. This is the cleanest solution, but does require an additional library.

confusion about chaining $q promises with angular and typescript

I'm pretty inexperienced with Q, promises, angular, and typescript - so it seemed the obviously intelligent choice was to try using them all together!
Basically I have some methods setup to return Q.IPromise<T> so that I can chain a then(f) statement onto them. Both of them work fine;
public Clear = (): Q.IPromise<IArticle> => {
var deferred = this.$q.defer();
this.$http({
url: String.format('{0}{1}', this.$settings.uri(), '/api/cancel/article'),
responseType: 'json',
method: 'GET',
withCredentials: true
}).then((response: IArticleRequest) => {
deferred.resolve(response.data.article);
}, (response) => {
deferred.reject(null);
});
return deferred.promise;
}
public Fetch = (id: string):Q.IPromise<IArticleRequestData> => {
var deferred = this.$q.defer();
this.$http({
url: String.format('{0}{1}{2}', this.$settings.uri(), '/api/articles/fetch/', id),
responseType: 'json',
method: 'GET',
withCredentials: true
}).then((response: IArticleRequest) => {
deferred.resolve(response.data);
}, (response) => {
deferred.reject(null);
});
return deferred.promise;
}
So I can use them like this;
$articles.Fetch(1).then((r: IArticleRequestData) => {
$articles.Clear().then((n:IArticle) => {
console.log('completed!');
})
});
That works fine - and as I expect it to.
But now, I need another step - a last call sort of. Another then(f) function that goes on the outermost function but doesn't occur until the innermost one completes.
So in short, I need this;
$articles.Fetch(1).then((r: IArticleRequestData) => {
$articles.Clear().then((n:IArticle) => {
// inner function completed
})
}).then((l:any)=> {
// occurs after everything else is done and inner
// functions are finished
});
and I'm having a very difficult time figuring out how to make this happen. Is this even possible with the way promises work?
extra notes
adding another function to the inner section is allowable but it feels like the wrong approach. this is my go to "fix" if I absolutely cannot figure out the way I'm asking about, or if the way I'm trying to do it is just wrong.
I want the extra then on the outside because the inner methods are actually responsible for resolving certain things, and as such they may split off into other tangents or deferred methods as well.
Just return the promise from the inner function like this:
$articles.Fetch(1).then((r: IArticleRequestData) => {
return $articles.Clear()
}).then((l:any)=> {
// occurs after everything else is done and inner
// functions are finished
});

Function being removed from object during return

I'm trying to add an abort function to my promise in Angular. When debugging the code, I can see the function gets added as expected. However, when the object is returned to the calling service, the function is no longer there. I'm hoping it's something trivial.
.factory('MatchmakerSearch', ['$resource', 'OBB_ENV_CONF', '$q', function ($resource,
OBB_ENV_CONF, $q) {
// Create the $resource object to handle the API requests
function _query(params) {
var _deferredAbort = $q.defer();
var _request = $resource(OBB_ENV_CONF.API_HOST + 'int/matchMaker', {}, {
'query': {
method: 'GET',
params: params,
isArray: false,
timeout: _deferredAbort.promise
}
});
var _promise = _request.query().$promise.then( // Convert from $resource to $http
function (response) {
return response;
}, function (response) {
return $q.reject('Ajax call aborted');
}
);
_promise.abort = function () {
_deferredAbort.resolve();
};
_promise.finally(function () {
_promise.abort = angular.noop;
_deferredAbort = _request = _promise = null;
});
return _promise; // <~~~~ abort function exists here
}
return {
query: _query
}
}
]);
The service making the call looks like this:
_searchRequest = MatchmakerSearch.query(buildQueryParams()).then(function (result) {
// <~~~~ _searchRequest does not contain an abort() function.
});
I really thought this would be a simple thing to code. Any ideas on why my function is disappearing on return?
Every time you chain a promise with then, catch, or finally you receive a new Promise back:
_searchRequest = MatchmakerSearch
.query(buildQueryParams()) // Your customised promise.
.then(function (result) {}) // a new promise returned here.
So _searchRequest ends up being a fresh new Promise instance.
The documentation for deferred.then() talks about this:
then(successCallback, errorCallback, notifyCallback) – ...
This method returns a new promise which is resolved or rejected via the return value of the successCallback, errorCallback ...
(Emphasis in the original)

How can I return a success and an error promise with and without data?

I would like to return a promise and an object called output either before or after the $http call. Can someone advise me how I can do this with the AngularJS framework and very important with Typescript so I can be sure it is working correctly?
topicNewSubmit = (): ng.IPromise<any> => {
var self = this;
var myData1 = { abc: 123 }
if (self.abc = 22) {
// How can I return an OKAY promise from here?
}
if (self.abc = 33) {
// How can I return an OKAY promise with myData1 from here?
}
if (self.abc = 88) {
// How can I return a FAIL promise from here?
}
return self.$http({ url: self.url, method: "GET" })
.then(
(response: ng.IHttpPromiseCallbackArg<any>): any => {
var myData2 = { abc: 245 }
// How can I return a promise and myData2.
// return(myData2) gives an error with Typescript
// How can I return a promise and no data
// return gives an error with Typescript
},
(error: ng.IHttpPromiseCallbackArg<any>): ng.IPromise<any> => {
var myData3 = { abc: 345 }
// Is this the correct way to return an unhandled reject with myData3
return self.$q.reject(myData);
});
}
Edit: Fixed the code and added a TypeScript Playground example. The methods are correctly typed, you can verify this by the typing errors that are thrown, try and fix them ;). I copied over the very basic required interfaces from the angular definition file.
Edit #2: Here's the fixed version of the TypeScript Playground example above.
If I understand your question correctly, you're trying to define a return type for the service method stating it returns a promise whose result upon resolving will be a certain object?
In that case you're almost there, I've split up your two example methods in separate blocks, as they require a different approach.
On a general note, I removed the scope copying (self = this) as you are using the fat arrow methods, which will automatically scope the method to the outer lexical scope. In short, there's no need to do the scope copy and in fact, in your example self doesn't always point to the service (as you're copying the scope inside of the method instead of outside of it).
Also, please note the definition of an Angular promise (truncated):
interface IDeferred<T> {
resolve(value?: T): void;
reject(reason?: any): void;
}
As such, typing an Angular Promise will only add a typing for the resolve case of the promise, not for the rejected case. Consequently, when calling your service, it will verify that the result in the success handler is of the type you have defined, but the type of the parameters in the error handler is of type any.
topicTest
For this method to work you need to inject $q into your service and then use it to create your own deferred
topicTest = (): IPromise<Foo> => { // return a promise which will result in a parameter of MyType upon resolving
var deferred = this.$q.defer<Foo>(); // Type the deferred to get better 'intellisense' support
if (this.abc = 99) {
deferred.resolve(new Foo());
}
if (this.abc = 88) {
deferred.reject("You can pass in whatever you like to the reject case");
}
return deferred.promise;
};
topicNewSubmit
The $http already returns promises, so you only need to hook into these by attaching a then callback and returning from that method to allow chaining other then callabacks to it.
In that case the return type of your service method would be angular.IPromise<() => any> where you can replace any with a type you'd like. The return type of the then method would have to correspond to whatever type you chose for the generic placeholder in the return type of the service method.
topicNewSubmit = () : IPromise<Foo> => {
return this.$http({ url: this.url, method: "GET" }).then((response): Foo => {
return new Foo();
}, (error) => {
return "whatever you'd like, it does not have to correspond to Foo";
});
}
You could then use your service like
MyService.topicNewSubmit().then((data) => {
// data needs to be of type T, where T is the type you defined in the generic placeholder of IPromise<T>
}, (error: any) => {
// In the error case, the parameters are of type any
});
To be honest, I feel like I'm shooting in the dark with your code sample, but here's my solution.
Like Anzeo, I removed the references to self or this. $q and $http should be assigned somewhere.
declare var $q: ng.IQService;
declare var $http: ng.IHttpService;
var topicNewSubmit = (): ng.IPromise<any> => {
var deferred = $q.defer<any>();
var myData1 = { abc: 123 };
if (this.abc = 22) {
deferred.resolve();
} else if (this.abc = 33) {
deferred.resolve(myData1);
} else if (this.abc = 88) {
deferred.reject();
} else {
$http({
url: this.url,
method: "GET"
})
.then(() => {
deferred.resolve({ abc: 245 });
}, () => {
deferred.reject({ abc: 345 });
});
}
return deferred.promise;
};
Answer
You cannot return something that isn't there yet unless you block all operations till you get it. Since the browser JavaScript is (mostly) single threaded you don't want to block the thread while the file downloads. Hence you return a promise that will eventually resolve. The consumer needs to use the then function to eventually get the value.
Humor
A promise is for life. Its 🐢s all the way down 🌹
You can use promise like
var deferred = $q.deferred();
$http(api url and method)
.success function() {
deferred.resolve();
}
.failure function() {
deferred.reject();
}
return deferred.promise;

How should I declare the return type of a function that returns a $q promise with no data?

I have this function:
getMethod = (): ng.IPromise<any> => {
if (1 == 2) {
return this.$q.when();
}
var defer = this.$q.defer();
this.$http({
url: '/abc',
method: "GET"
})
.success((): void => {
//
defer.resolve();
})
.error((): void => {
//
defer.reject();
})
return defer.promise;
}
This works okay and the promises returned do not contain any data.
So I changed the first line to:
getMethod = (): ng.IPromise<void> => {
Now I get an error:
Error 3 Cannot convert 'ng.IPromise<{}>' to 'ng.IPromise':
Types of property 'then' of types 'ng.IPromise<{}>' and 'ng.IPromise' are incompatible:
Can someone give me advice on how I should correctly declare the return type. I know using works but it does not seem a clean solution if the function really always returns void.
I'm not sure if I understand what you are trying to do , but this transpiles without errors
getMethod = (): ng.IPromise<void> => {
var defer:ng.IDeferred<void> = this.$q.defer<void>();
this.$http({ url: '/abc', method: "GET"}).then(
payload => defer.resolve(),
error=> defer.reject()
);
return defer.promise;
}
It could probably mean that you want to know the operation completed
I'd've choose observer pattern, messaging ,a callback?
it seems to me that promises are better to encapsulate values, maybe_value,

Categories

Resources