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) {
...
});
Related
I am recursively calling a function which returns a Promise, but i am noticing that the next then is called even if i am rejecting with error. Below is the relevant part of the code.
const applyFilters = (counter) => {
return new Promise((resolve, reject) => {
let filter = filters[counter];
if(filter) {
applyStep(filter).then(promiseTimeout(filter.delay), function(err) {
console.log('reject with error');
reject(err);
}).then(function(res) {
console.log('still executing after reject');
resolve(applyFilters(++counter).catch(function(err) {
reject(err);
}));
});
} else {
resolve(true);
}
});
};
const applyStep = (step) => {
if(step.step_type == 'filter') {
return worksheet.applyFilterAsync(step.name, values, 'replace');
} else if(step.step_type == 'parameter') {
return workbook.changeParameterValueAsync(`${step.name}`, value);
} else {
return Promise.resolve(true);
}
};
I am seeing on console
reject with error
still executing after reject
Is this the expected behaviour, may be I am missing something. Any help in understating this further will be really great. Thanks.
You are passing the second callback to then, which handles the error (in this case by rejecting the outer promise), and then fulfills the promise returned by the then() call with the callback return value (undefined). The next then() in the chain will be executed once that promise is fulfilled.
I could tell you how to work around this problem by using a different then/catch structure, but really you need to avoid the Promise constructor antipattern here!
function applyFilters(counter) {
if (counter >= filter.length)
return Promise.resolve(true);
const filter = filters[counter];
return applyStep(filter)
.then(promiseTimeout(filter.delay))
.then(res => applyFilters(++counter));
}
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.
I have some javasript code that takes an existing promise
(say, the promise returned by fetch()) and adds value
(say, then/catch listeners for debugging, or maybe more):
let myFetch = function(url) {
return fetch(url).then(function(value) {
console.log("fetch succeeded: value=",value);
return value;
}.catch(function(reason) {
console.log("fetch failed: reason=",reason);
throw reason;
});
};
I found myself modifying the above code so that the listeners are added only if some condition is true:
let myFetch = function(url) {
let promise = fetch(url);
if (some condition) {
promise = promise.then(function(value) {
console.log("fetch succeeded: value=",value);
return value;
}.catch(function(reason) {
console.log("fetch failed: reason=",reason);
throw reason;
});
}
return promise;
};
Now I'm wondering, does it really make sense for myFetch to return the new promise returned by "then"
(actually catch which is shorthand for another "then") as above,
or would it make more sense for it to return the original promise (with the added listeners)?
In other words, I'm thinking of leaving out the second "promise =",
so that the code will look like this instead:
let myFetch = function(url) {
let promise = fetch(url);
if (some condition) {
promise.then(function(value) {
console.log("fetch succeeded: value=",value);
return value;
}.catch(function(reason) {
console.log("fetch failed: reason=",reason);
throw reason;
});
}
return promise;
};
Is that effectively different from the previous version?
Is either version preferable, and if so, why?
If your only use case is logging something in then/catch – it shouldn't matter as long as everything goes well. Things get more messed if you get an exception. Consider these two examples:
Return original promise
function myFetch() {
let promise = new Promise(function (resolve, reject) {
resolve(100);
});
promise.then(function () { throw new Error('x'); });
return promise;
}
myFetch().then(function () {
console.log('success!');
}).catch(function (e) {
console.error('error!', e)
});
The result is success and the error thrown in the inner then might get swallowed in some promise libraries (although the most popular ones such as Bluebird handle this and you get additional error Unhandled rejection Error: x).
The error might also get swallowed when using native Promises in some environments.
Returning modified promise
function myFetch() {
let promise = new Promise(function (resolve, reject) {
resolve(100);
});
promise = promise.then(function () { throw new Error('x'); });
return promise;
}
myFetch().then(function () {
console.log('success!');
}).catch(function (e) {
console.error('error!', e)
});
Now the result is error! Error: x.
Well, if your success handler returns the value and your rejection handler throws the error - then it is basically the identity transformation for the promise.
Not only do you not need to do that promise = promise.then you don't even need to return the values:
let myFetch = function(url) {
let promise = fetch(url);
if (some condition) {
promise.then(function(value) {
console.log("fetch succeeded: value=",value);
}.catch(function(reason) {
console.log("fetch failed: reason=",reason);
});
}
return promise;
};
That said, if you're using ES6 and let, you can use arrow functions which makes this a little nicer anyway:
let myFetch = function(url) {
let promise = fetch(url);
if (some condition) {
promise.then(value => console.log("fetch succeeded: value=",value))
.catch(reason => console.log("fetch failed: reason=",reason));
}
return promise;
};
Some promise libraries like bluebird provide a tap utility for this. The only problem is that if ever fetch adds support for promise cancellation, you are breaking the chain with the if (some condition) handler if you're not chaining it.
You're promise branching. In the second case, you're effectively branching the promise chain into two promise chains, because once a caller calls myFetch:
myFetch("localhost").then(request => { /* use request */ } );
then promise will have had .then called on it twice (once inside myFetch to do the console logging, and again here).
This is fine. You can call .then on the same promise as many times as you like, and the functions will execute together in the same order whenever promise resolves.
But, importantly, each function represents a branch off of the original promise, independent of every other branch. This is why you don't need to return or rethrow anything after your console.log: Nobody's listening on that branch, specifically, the caller of myFetch is not affected.
This is a good fit for logging IMHO, but there are subtle timing and error handling differences to be aware of when doing anything more:
var log = msg => div.innerHTML += msg + "<br>";
var myFetch = url => {
var p = Promise.resolve({});
p.then(() => log("a")).then(() => log("b"));
return p;
}
myFetch().then(() => log("1")).then(() => log("2")).catch(log); // a,1,b,2
<div id="div"></div>
This emits a,1,b,2. As you can see, there are two chains going on here, advancing in parallel. It makes sense when you think about when promise is resolved, but it can be surprising.
The other subtlety is that error handling is also per branch (one branch will never fail another). In fact, the above code has a bug. Did you spot it? There should be a catch after .then(() => log("b")), or errors about anything you do in that branch end up unhandled or swallowed in some environments.
I have a function which is going to look up the cache if it finds anything, otherwise then then it will go ahead and fetch the data and set the cache. It's pretty standard. I'm wondering if the error occurs at the most inner function, will it keep bubble up to the outer most Promise? So, I can just have one catch instead of one.
Here's my code.
I'm using Bluebird
var _self = this;
return new Promise(function(resolve, reject) {
_self.get(url, redisClient).then(function getCacheFunc(cacheResponse) {
if(cacheResponse) {
return resolve(JSON.parse(cacheResponse));
}
webCrawl(url).then(function webCrawl(crawlResults) {
_self.set(url, JSON.stringify(crawlResults), redisClient);
return resolve(crawlResults);
}).catch(function catchFunc(error) {
return reject(error); // can I delete this catch
});
}).catch(function getCacheErrorFunc(cacheError) {
return reject(cacheError); // and let this catch handle everything?
});
});
Yes, it is possible to have a single .catch(...) for deeply-nested Promises. The trick: you can resolve a Promise with another Promise. This means that you can refactor your code to:
var _self = this;
_self.get(url, redisClient)
.then(function(cacheResponse) {
if(cacheResponse) {
// Resolve the Promise with a value
return JSON.parse(cacheResponse);
}
// Resolve the Promise with a Promise
return webCrawl(url)
.then(function(crawlResults) {
_self.set(url, JSON.stringify(crawlResults), redisClient);
// Resolve the Promise with a value
return crawlResults;
});
})
.catch(function(err) {
console.log("Caught error: " + err);
});
Note: I also removed your outermost Promise declaration. This was no longer necessary since _self.get(...) already returned a Promise.
Assuming that .get returns a Promise you would write it that way:
var _self = this;
return _self.get(url, redisClient).then(function(cacheResponse) {
if (cacheResponse) {
return JSON.parse(cacheResponse);
} else {
return webCrawl(url).then(function(crawlResults) {
_self.set(url, JSON.stringify(crawlResults), redisClient);
return crawlResults;
})
}
});
There is no need to introduce a new Promise because you already get one from your _self.get
As a node programmer. I'm used to use "nodebacks" for handling errors in my code:
myFn(param, function(err, data) {
if (err){
//error handling logic
}
else {
// business logic
}
});
When writing that function, I can do something like:
var myFn = function(param, callback){
var calc = doSomeCalculation(param);
if(calc === null) { // or some other way to detect error
callback(new Error("error with calculation"), null);
}
...
someAsyncOp(calcN,function(err, finalResult){
if(err) return callback(err, null);
callback(null, finalResult); // the error is null to signal no error
});
};
How would I do this sort of error handling with promises?
Rule of Thumb
Whenever you have a doubt about how to do something with promises - think about the synchronous version.
try{
var result = myFn(param);
// business logic with result
} catch(e) {
//error handling logic
}
This, at least to me looks a lot cleaner than a callback with a first parameter that is sometimes null.
The promises way is almost always very similar to the synchronous version of the problem:
myFn(param).then(function(result){
// business logic with result
}).catch(function(e){
//error handling logic
});
Where myFn would look something like when working with callbacks:
var myFn = function(param){
return new Promise(function(resolve, reject){
var calc = doSomeCalculation(param);
if(calc === null) { // or some other way to detect error
reject(new Error("error with calculation"), null);
}
someAsyncOp(calcN,function(err, finalResult){
if(err) reject(err);
resolve(finalResult);
})
});
};
Working with callbacks/nodebacks
This is only something you should have to do when working with callbacks, when working with promises it is a lot simpler, and you can do:
var myFn = function(param){
var calc = doSomeCalculation(param);
...
return someAsyncOp(calcN); // returning a promise.
}
Moreover, when working inside promise chains, you get throw safety:
myFn(param).then(function(calcN){
// here, you throw to raise an error and return to resolve
// new Promise should be used only when starting a chain.
}).catch(function(err){
// handle error
}).then(function(){
// ready to go again, we're out of the catch
});
Note, some libraries like Bluebird , RSVP and Q offer syntactic sugar and automatic promisification of methods so you rarely have to use new Promise yourself.
Also, consider reading this and that to learn more about promise error handling.
If you're using the async/await syntax, you can just use the regular try-catch syntax for error handling.
// your promise function
const myFn = function(param){
return new Promise(function(resolve, reject){
if (someLogic()) {
resolve(someValue);
} else {
reject('failure reason');
}
});
}
// Define the parent function as an async function
async function outerFn(param) {
try {
// Wait for the promise to complete using await
const result = await myFn(param)
// business logic with result
} catch (e) {
//error handling logic
}
}