migrate deprecated promise-handling code - javascript

I have some code that handles Angular promises like this
somethingThatReturnsAPromise
.then(function (data) {
// handle success
})
.catch(function (error) {
// handle error
})
.finally(function () {
// always do this
});
I understand this syntax is deprecated now, and this code should be replaced with
somethingThatReturnsAPromise.then(
function (data) {
// handle success
},
function (error) {
// handle error
}
);
But where should I put the code that was previously in finally when using this new syntax, i.e. code that is executed both when the promise is resolved (successful) and rejected (fails)?

If you want to go with then either way, you can provide a handler for both success and error promise handlers:
function always() {
// Do whatever either it fails or succeeds
}
somethingThatReturnsAPromise.then(always, always).then(function(data) {
}, function(error) {
});

1st: I didn't find anything about any (Promise-related) method being deprecated in the official docs.
2nd: finally is way more complicated then a then(cb, cb), since it doesn't catch Errors, and doesn't propagate the results of your callback, but if you return a promise, it waits for this promise to resolve until it continues propagating the current value.
Sth like this:
function _finally(promise, callback) {
var handleValue = isError => value => $q((resolve, reject) => {
//call your callback
//if this throws, propagate the Error
var w = typeof callback === "function" && callback();
//prepare to push the current value/error
var fn = isError?
() => reject(value):
() => resolve(value);
//check wether your callback has returned sth. Promise-like
if(w && typeof w.then === "function"){
//then we'll wait for this to resolve,
//before we continue propagating the current value/error
w.then(fn, fn);
}else{
//otherwise propagate the current value/error emmediately
fn();
}
});
return $q.resolve(promise).then(
handleValue(false),
handleValue(true)
);
}
I wrote this code only to give you an implession what finally does. Angular's implementation is smoother, so stick with that.
I don't see any reason to think that catch or finally are deprecated or will ever be.

Related

Implementing Simple Promise's finally method

I was trying to implement promise from scratch.
Question:
I wasn't sure how do I implement Finally? (I'm guessing finally will
execute after then's and the catch is invoked. (ONLY once))
If you feel any refactors can be made to my code, please feel free to suggest. This is my naive attempt to implement promises.
This is my implementation:
function Promisify(fn) {
let status = 0; // 0 = unfulfilled, 1 = resolved, 2 = rejected
let result;
let error;
let thenFns = [];
let catchFns = [];
let finallyFn = undefined;
// Public Methods.
this.then = function(fn) {
thenFns.push(fn);
doThen();
return this; // for chaining
};
this.catch = function(fn) {
catchFns.push(fn);
doCatch();
return this; // for chaining
};
// TODO: Implement finally
this.finally = function(fn) {
finallyFn = fn;
// dofinally(fn);
return this;
}
// Private Methods
function resolve(r) {
if (status) throw Error('can not resolve, already handled');
status = 1;
result = r;
doThen();
}
function reject(e) {
if (status) throw Error('can not reject, already handled');
status = 2;
error = e;
doCatch();
}
function doThen() {
if (status === 1) {
while(thenFns.length) {
thenFns.shift()(result);
}
}
}
function doCatch() {
if (status === 2) {
if (catchFns.length === 0) {
console.error('uncaught error')
}
while(catchFns.length) {
catchFns.shift()(error);
}
}
}
try {
fn(resolve, reject);
} catch (e) {
reject(e);
}
}
// ======== QUESTION: Caller ================
const demoFail = new Promisify((resolve, reject) => {
setTimeout(function() {
reject('Howdy! from DemoFail')
}, 1000);
});
demoFail
.then(val => console.log("DemoFail Then!!"))
.catch(console.error) //Should throw an error.
.then(val => console.log("Second then!"))
.catch(
(err) => {
throw new Error('error', err);
})
.finally(val => console.log("Executed Finally"))
Here are some things missing from your implementation:
.then() needs to return a new promise, not the same promise.
The return value of a .then() handlers (if it's not a promise) becomes the resolved value of the newly returned promise returned in step #1.
If the return value of a .then() handler is a promise, then the newly returned promise from step 1 doesn't resolve until the promise returned from the .then() handler resolves or rejects with that value.
When calling a .then() handler, you need try/catch around the call to the handler because if the handler throws, then that turns the promise returned into step #1 into a rejected promise with the throw error as the reject reason.
There is similar logic for all the .catch() handlers (it also returns a new promise and the return value of the handler affects the newly returned promise from step #1).
When you store .then() or .catch() callback fns, you also need to store the newly create promise that you returned separate for each callback because that promise will be affected by the return value or exception thrown in the callback.
The spec itself that covers this is fairly simple. You can read it here. And, here's an implementation of that spec.
Your simple version looks like it probably works for one level of promise like fn().then(), but it doesn't do proper chaining and doesn't catch exceptions in handlers and doesn't pay attention to return values in handlers which are all pretty fundamental behaviors of promises. Unfortunately, there is no super simple way to write promises that includes the fundamental behaviors. It just takes more code to support them.

Force an Asynchronous call to behave Synchronously

In my React app, I'm trying to calculate a value based on three other values. I've contained all of the calculation logic to the back end, which is a microservice I make asynchronous calls to. The function in which I am asynchronously trying to get that calculated value is in the middle of many synchronous hooks.
In the UI layer, I call the function which I want to return the end result (returned asynchronously). That called function calls another function, which calls another function, which returns a new Promise. See code below:
// DateUI.js (layer 1)
selectDate(dateField, flight, idx, saved, momentTime, e) {
if (moment(momentTime).isValid()) {
if (dateField == "StartDate") {
// The initial problematic function call, need to set endDate before I continue on
let endDate = PlanLineActions.calculateFlightEndDate(periodTypeId, numberOfPeriods, momentTimeUnix);
flight.set("EndDate", endDate);
}
this.theNextSyncFunction(..., ..., ...);
}
}
// DateActions.js (layer 2)
calculateFlightEndDate(periodTypeId, numberOfPeriods, startDate) {
let plan = new Plan();
plan.getFlightEndDate(periodTypeId, numberOfPeriods, startDate).then(function(response) {
// response is JSON: {EndDate: "12/05/2016"}
response.EndDate;
}, function(error) {
log.debug("There was an error calculating the End Date.");
});
}
// DateClass.js (layer 3)
getFlightEndDate(periodTypeId, numberOfPeriods, startDate) {
let path = '/path/to/microservice';
return this.callServer(path, 'GET', {periodTypeId: periodTypeId, numberOfPeriods: numberOfPeriods, startDate: startDate});
}
// ServerLayer.js (layer 4)
callServer(path, method = "GET", query = {}, data, inject) {
return new Promise((resolve, reject) => {
super.callServer(uri.toString(),method,data,inject).then((data) => {
resolve(data);
}).catch((data) => {
if (data.status === 401) {
AppActions.doRefresh();
}
reject(data);
});
});
}
I am under the impression that, because ServerLayer.js (layer 4) returns a new Promise (and thus DateClass.js (layer 3)), calling plan.getFlightEndDate(...).then(function(response) {... will not complete until the response comes back resolved or rejected. This is not currently happening, as the code in DateUI.js (layer 1) will continue on to call this.theNextSyncFunction, and then resolve ~50ms later with the proper data.
How do I force PlanLineActions.calculateFlightEndDate(...) in DateUI.js (layer 1) to complete with a response before I continue on with selectDate()?
You can't force something to be synchronous if it goes outside of the event loop like an ajax call. You're going to need something that looks like this:
PlanLineActions.calculateFlightEndDate(periodTypeId, numberOfPeriods, momentTimeUnix)
.then(endDate => {
this.theNextSyncFunction(..., ..., ...);
})
In order to do this, calculateFlightEndDate will also need to return a promise, and thus it's a good thing that promises are chainable.
calculateFlightEndDate(periodTypeId, numberOfPeriods, startDate) {
let plan = new Plan();
// return promise!
return plan.getFlightEndDate(periodTypeId, numberOfPeriods, startDate).then(response => {
return response.EndDate; // must return here
}, error => {
log.debug("There was an error calculating the End Date.");
});
}
That should do it.. and one more thing: you're doubling up on promises in your server call. If something has .then it's already a promise, so you can just return that directly. no need to wrap in new Promise (a promise in a promise.. no need!)
callServer(path, method = "GET", query = {}, data, inject) {
// just return!
return super.callServer(uri.toString(),method,data,inject).then((data) => {
return data;
}).catch((data) => {
if (data.status === 401) {
AppActions.doRefresh();
}
throw data; // throw instead of reject
});
}
I think you don't understand how promises work.
First of all, functions always return immediately so you won't ever block execution of the next lines of code (flight.set and theNextSyncFunction() in your case). This is the point of returning a promise: you get a promise immediately that you can attach a callback to (using then()) that will get invoked later. If you want code to wait for the promise to resolve you have to put it in a then() callback.
Secondly, your calculateFlightEndDate() is not returning anything at all so endDate = calculateFlightEndDate() is simply setting endDate to undefined.
Solution
You should return a promise from calculateFlightEndDate() and put the code you want to execute afterwards inside a then() callback:
calculateFlightEndDate(periodTypeId, numberOfPeriods, startDate) {
let plan = new Plan();
return plan.getFlightEndDate(periodTypeId, numberOfPeriods, startDate).then((response) => {
// response is JSON: {EndDate: "12/05/2016"}
return response.EndDate;
}, (error) => {
log.debug("There was an error calculating the End Date.");
});
}
if (moment(momentTime).isValid()) {
if (dateField == "StartDate") {
PlanLineActions.calculateFlightEndDate(periodTypeId, numberOfPeriods, momentTimeUnix).then((endDate) => {
flight.set("EndDate", endDate);
this.theNextSyncFunction(...);
});
}
}
You could also look into using ES7 async and await which allows you to write your async code so that it looks synchronous, but uses promises under the hood to accomplish the same thing.

Continue of failure of jQuery Deferred chain

I'm doing a series of sequential AJAX calls in jQuery, using the usual method of chaining with Deferred. The first call returns a list of values and the subsequent calls are made with those returned list entries. After the first call that returns the list, the subsequent calls may be done in any order, but they must be done one at a time. So this is what I use:
$.when(callWebService()).then(
function (data) {
var looper = $.Deferred().resolve(),
myList = JSON.parse(data);
for (var i in myList) {
(function (i) {
looper = looper.then(function () { // Success
return callWebService();
},
function (jqXHR, textStatus, errorThrown) { // Failure
if (checkIfContinuable(errorThrown) == true)
continueChain();
else
failWithTerribleError();
});
})(i);
}
});
It turns out that the subsequent calls may fail at times, but I still want to do the remainder of the calls. In my listing, that's what this little bit of inventive pseudo code is meant to do:
if (checkIfContinuable(errorThrown) == true)
continueChain();
else
failWithTerribleError();
How on earth do I implement continueChain though? It appears as though a failure on any deferred will cause the rest of the chain to also fail. Instead, I'd like to log the error and continue with the rest of the list.
With Promises/A+ this is as easy as
promise.then(…, function(err) {
if (checkIfContinuable(err))
return valueToConinueWith;
else
throw new TerribleError(err);
})
Unfortunately, jQuery is still not Promises/A+ compliant, and forwards the old value (result or error) - unless you return a jQuery Deferred from the callback. This works just the same way as rejecting from the success handler:
jDeferred.then(…, function(err) {
if (checkIfContinuable(err))
return $.Deferred().resolve(valueToConinueWith);
else
return $.Deferred().reject(new TerribleError(err));
})
Recovering from an error in a jQuery promise chain is more verbose than with Promises/A+ implementations, which naturally catch errors in a .catch's or a .then's error handler. You have to throw/rethrow in order to propagate the error state.
jQuery works the other way round. A .then's error handler (.catch doesn't exist) will naturally propagate the error state. To emulate "catch", you have to return a resolved promise, and the chain will progress down its success path.
Starting with an array of items on which you want to base a series of async calls, it's convenient to use Array.prototype.reduce().
function getWebServiceResults() {
return callWebService().then(function(data) {
var myList;
// This is genuine Javascript try/catch, in case JSON.parse() throws.
try {
myList = JSON.parse(data);
}
catch (error) {
return $.Deferred().reject(error).promise();//must return a promise because that's what the caller expects, whatever happens.
}
//Now use `myList.reduce()` to build a promise chain from the array `myList` and the items it contains.
var promise = myList.reduce(function(promise, item) {
return promise.then(function(arr) {
return callWebService(item).then(function(result) {
arr.push(result);
return arr;
}, function(jqXHR, textStatus, errorThrown) {
if(checkIfContinuable(errorThrown)) {
return $.when(arr); // return a resolved jQuery promise to put promise chain back on the success path.
} else {
return new Error(textStatus);//Although the error state will be naturally propagated, it's generally better to pass on a single js Error object rather than the three-part jqXHR, textStatus, errorThrown set.
}
});
});
}, $.when([])) // starter promise for the reduction, resolved with an empty array
// At this point, `promise` is a promise of an array of results.
return promise.then(null, failWithTerribleError);
});
}
Notes:
An overall function wrapper is assumed, function getWebServiceResults() {...}.
callWebService() is assumed to accept item - ie the contents of each element of myList.
To do its job, checkIfContinuable() must accept at least one argument. It is assumed to accept errorThrown but might equally accept jqXHR or textStatus.

Bluebird Promise Cancellation

Say I have the following Promise chain:
var parentPromise = Promise.resolve()
.then(function () {
var condition = false;
if (condition) {
return parentPromise.cancel('valid reason');
} else {
return Promise.resolve()
.then(function () {
var someOtherCondition = true;
if (someOtherCondition) {
console.log('inner cancellation');
return parentPromise.cancel('invalid reason');
}
});
}
})
.catch(Promise.CancellationError, function (err) {
console.log('throwing');
if (err.message !== 'valid reason') {
throw err;
}
})
.cancellable();
The above never enters the catch.
If we swap condition to true, the inner cancellation is never hit, but the catch is still not triggered.
removing the .cancellable at the end , and replacing all instances of parentPromise.cancel() with explicit throw new Promise.CancellationError() "fixes" the problem. What I don't understand is why?
Why was the original approach not working?
I am using bluebird 2.3.11.
cancellable() creates cancellable promises and only they throw CancellationError by default, when cancel function is called with out any reason.
In your case, you are making the promise cancellable only after attaching the catch handler. But the promise is not cancellable yet. So, cancel function call will not raise Promise.CancellationError.
You need to change the structure of the code, like this
then(function(..) {
...
})
.cancellable()
.catch(Promise.CancellationError, function (err) {
...
});
Note: I would recommend promising with its beautiful Promise constructor function. It is similar to the ECMA Script 6 specification.
new Promise(function(resolve, reject) {
...
});

Promises .then() with one argument

I am having trouble interpreting what the Promises/A+ spec meant by this...
https://promisesaplus.com/#point-23
If you have a promise and call .then with one argument, does that mean that the single argument will be called regardless of success or failure?
I feel like it could go either way with interpreting this. I guess the library I would be most concerned with is Q.
The first argument to a .then() handler, whether it is the only argument or not, is always the fulfilled handler and is only called if the promise is fulfilled. That first argument is not treated differently if there is or is not a second argument passed.
So to anyone who is unsure, I made a test script that you can run in your node REPL. The definitive answer is that no it will not call the success with an err attachment.
var Q = require('q');
//call with whether or not the promise will resolve
function aPromise (bool) {
var def = Q.defer();
if (!bool) {
def.reject("oooo0o0o00o0 rejected");
}
def.resolve('winner winner chicken dinner');
return def.promise
}
var whatGonnaBe = aPromise(false)
//The Example
whatGonnaBe
.then(function (response) {
console.log('1')
console.log(JSON.stringify(response) + '1');
})
.then(function (response) {
console.log('2')
console.log(JSON.stringify(response) + '2');
},
function (response) {
console.log('3')
console.log(JSON.stringify(response) + '3');
})
No, the first argument is always the "success" or "resolved" argument. The second argument is always the failure argument, and you can omit it when chaining promises together and have a single "fail" argument to handle all fails. This code is just conceptual:
promise1.then(function() {
return promise2;
}).then(function() {
return promise3;
}).then(function() {
/* everything succeeded, move forward */
}, function() {
/* catch any failure by any of the promises */
});

Categories