I'm working on an application that uses what-wg fetch all over the place. We've defined default fetch middleware and options this way:
export function fetchMiddleware(response) {
return new Promise(resolve => {
resolve(checkStatus(response));
}).then(parseJSON);
}
export const fetchDefaults = {
credentials: 'same-origin',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
};
We use our default middleware / fetch options this way:
fetch('/api/specific/route', fetchDefaults)
.then(fetchMiddleware)
.then(function(data) {
// ... Dispatch case-specific fetch outcome
dispatch(specificRouteResponseReceived(data));
});
We want to add a generic, fallback catch to all fetch usages throughout the application, in other words something like this:
export function fetchGenericCatch(function(error) {
showGenericErrorFlashMessage();
})
fetch('/en/api/user/me/preferences', fetchDefaults)
.then(fetchMiddleware)
.then(function(data) {
dispatch(userPreferencesReceived(data));
})
.catch(fetchGenericCatch);
Lots of code duplication. We'd like a utility function / class which can do all of this for us, e.g. something which would work like this:
genericFetch('/api/specific/route') // bakes in fetchDefaults and fetchMiddleware and fetchGenericCatch
.then(function(data) {
dispatch(userPreferencesReceived(data));
}); // gets generic failure handler for free
genericFetch('/api/specific/route') // bakes in fetchDefaults and fetchMiddleware and fetchGenericCatch
.then(function(data) {
dispatch(userPreferencesReceived(data));
})
.catch(function(error) {
// ...
}); // short-circuits generic error handler with case-specific error handler
The main caveat is that the generic catch must be chained after the case-specific thens / catches.
Any tips on how this might be achieved using whatwg-fetch / ES6 Promises?
Related:
There are similar posts, but they don't seem to address the need for a default catch which runs after all non-default thens and catches:
How to create a Promise with default catch and then handlers
Default behavior if no other functions chained to a promise
Edit 14 Oct:
Possible duplicate: Promises and generic .catch() statements
Having WET code isn't the worst option here, as long as error handler is DRY.
fetch(...)
...
.catch(importedFetchHandler);
It causes no problems and conforms to the behaviour of Bluebird and V8 promises, where unhandled rejection event exists to make sure that no promise are left uncaught.
The most simple way to reach this is to introduce promise-like wrapper for fetch promise:
function CatchyPromiseLike(originalPromise) {
this._promise = originalPromise;
this._catchyPromise = Promise.resolve()
.then(() => this._promise)
.catch((err) => {
console.error('caught', err);
});
// every method but 'constructor' from Promise.prototype
const methods = ['then', 'catch'];
for (const method of methods) {
this[method] = function (...args) {
this._promise = this._promise[method](...args);
return this;
}
}
}
which can be used like
function catchyFetch(...args) {
return new CatchyPromiseLike(fetch(...args));
}
A promise-like like that has natural limitations.
Side effects are discarded if converted to real promise:
Promise.resolve(catchyFetch(...)).then(() => /* won't be caught */);
And it won't play well with asynchronous chain (this is a no-no for all promises):
var promise = catchyFetch(...);
setTimeout(() => {
promise.then(() => /* won't be caught */);
});
A good alternative is Bluebird, no need to invent the wheel there, features is what it is loved for. Local rejection events look like exactly what's needed:
// An important part here,
// only the promises used by catchyFetch should be affected
const CatchyPromise = Bluebird.getNewLibraryCopy();
CatchyPromise.onPossiblyUnhandledRejection((err) => {
console.error('caught', err);
});
function catchyFetch(...args) {
return CatchyPromise.resolve(fetch(...args));
}
Phew, that was easy.
I think the solution is as simple as:
export function genericFetch(url, promise, optionOverrides) {
const fetchOptions = {...fetchDefaults, ...optionOverrides};
return fetch(url, fetchOptions)
.then(fetchMiddleware)
.then(promise)
.catch(function(error) {
showGenericFlashMessage();
});
}
A use case which doesn't need a special error handler can simply use it this way:
genericFetch('/api/url', function(data) {
dispatch(apiResponseReceived(data));
});
A use case which needs a special catch, or a more complex chain, can pass in a full-blown promise:
genericFetch('/api/url', function(response) {
return new Promise(resolve, reject => {
dispatch(apiResponseReceived(data));
}).catch(nonGenericCaseSpecificCatch); // short-circuits default catch
});
Related
I'm writing a React application and in particular cases I have to resolve nested promises. The code works fine but I can't propagate the resolve() function up to the outer level, thus I'm not able to get the returning value.
This is the code:
writeData(data) {
this.store.dispatch({type: "START_LOADER"})
return new Promise((resolve, reject) => {
this.manager.isDeviceConnected(this.deviceId).then(res => {
this.manager.startDeviceScan(null, null, (error, device) => {
if (device.id === this.deviceId) {
resolve("test") // -> this is propagate correctly
device.connect().then((device) => {
this.store.dispatch({type: "SET_STATUS", payload: "Device is connected!\n"})
return device.discoverAllServicesAndCharacteristics()
}).then((device) => {
device.writeCharacteristicWithoutResponseForService(
data.serviceId,
data.charId,
data.dataToWrite
).then(res => {
resolve("test2") // -> this is not propagated
}).catch(error => {
reject(error.message)
})
}).catch((error) => {
reject(error.message)
});
}
});
}).catch(error => {
reject(error.message)
})
})
}
...
...
async writeAsyncData(data) {
await this.writeData(data)
}
When I call this function:
this.writeAsyncData({data}).then(response => {
// here I expect whatever parameter I have passed to resolve()
console.log(response)
})
In case I leave resolve("test") uncommented I can console.log it without any problem, but if I comment it, the resolve("test2") doesn't show in console.log and response is undefined.
How can I make sure that even the nested parameter of the inner resolve reach the console.log ?
To nest promises properly, you do NOT wrap them in yet another manually created promise. That is an anti-pattern. Instead, you return the inner promises and that will then chain them. Whatever the inner-most promise returns will be the resolved value for the whole chain.
In addition, when you have any asynchronous operations that return callbacks, you must promisify them so that you are doing all your asynchronous control flow with promises and can consistently do proper error handling also. Do not mix plain callbacks with promises. The control flow and, in particular, proper error handling gets very, very difficult. One you start with promises, make all async operations use promises.
While this code is probably simplest with async/await, I'll first show you how you properly chain all your nested promises by returning every single inner promise.
And, to simplify your nested code, it can be flattened so that rather than each level of promise making deeper indentation, you can just return the promise back to the top level and continue processing there.
To summarize these recommendations:
1. Don't wrap existing promises in another manually created promise. That's a promise anti-pattern. Besides being unnecessary, it's very easy to make mistakes with proper error handling and error propagation.
2. Promisify any plain callbacks. This lets you do all your control flow with promises which makes it a lot easier to avoid errors or tricky situations where you don't know how to propagate errors properly.
3. Return all inner promises from within the .then() handlers to properly chain them together. This allows the inner-most return value to be the resolved value of the whole promise chain. It also allows errors to properly propagate all the way up the chain.
4. Flatten the chain. If you have multiple promises chained together, flatten them so you are always returning back to the top level and not creating deeper and deeper nesting. One case where you do have to make things deeper is if you have conditionals in your promise chain (which you do not have here).
Here's your code with those recommendations applied:
// Note: I added a timeout here so it will reject
// if this.deviceId is never found
// to avoid a situation where this promise would never resolve or reject
// This would be better if startDeviceScan() could communicate back when
// it is done with the scan
findDevice(timeout = 5000) {
return new Promise((resolve, reject) => {
const timer = setTimeout(() => {
reject(new Error("findDevice hit timeout before finding match device.id"));
}, timeout);
this.manager.startDeviceScan(null, null, (error, device) => {
if (error) {
reject(error);
clearTimeout(timer);
return
}
if (device.id === this.deviceId) {
resolve(device);
clearTimeout(timer);
}
});
});
}
writeData(data) {
this.store.dispatch({type: "START_LOADER"});
return this.manager.isDeviceConnected(this.deviceId).then(res => {
return this.findDevice();
}).then(device => {
return device.connect();
}).then(device => {
this.store.dispatch({type: "SET_STATUS", payload: "Device is connected!\n"})
return device.discoverAllServicesAndCharacteristics();
}).then(device => {
return device.writeCharacteristicWithoutResponseForService(
data.serviceId,
data.charId,
data.dataToWrite
);
}).then(res => {
return "test2"; // this will be propagated
});
}
Here's a version using async/await:
findDevice(timeout = 5000) {
return new Promise((resolve, reject) => {
const timer = setTimeout(() => {
reject(new Error("findDevice hit timeout before finding match device.id"));
}, timeout);
this.manager.startDeviceScan(null, null, (error, device) => {
if (error) {
reject(error);
clearTimeout(timer);
return
}
if (device.id === this.deviceId) {
resolve(device);
clearTimeout(timer);
}
});
});
}
async writeData(data) {
this.store.dispatch({type: "START_LOADER"});
let res = await this.manager.isDeviceConnected(this.deviceId);
let deviceA = await this.findDevice();
let device = await deviceA.connect();
this.store.dispatch({type: "SET_STATUS", payload: "Device is connected!\n"})
await device.discoverAllServicesAndCharacteristics();
let res = await device.writeCharacteristicWithoutResponseForService(
data.serviceId,
data.charId,
data.dataToWrite
);
return "something"; // final resolved value
}
Note: In your original code, you have two overriding definitions for device. I left that there in the first version of the code here, but changed the first one to deviceA in the second one.
Note: As your code was written, if this.manager.startDeviceScan() never finds a matching device where device.id === this.deviceId, your code will get stuck, never resolving or rejecting. This seems like a hard to find bug waiting to happen. In the absolute worst case, it should have a timeout that would reject if never found, but probably the implementation of startDeviceScan needs to communicate back when the scan is done so the outer code can reject if no matching device found.
Note: I see that you never use the resolved value from this.manager.isDeviceConnected(this.deviceId);. Why is that? Does it reject if the device is not connected. If not, this seems like a no-op (doesn't do anything useful).
Note: You call and wait for device.discoverAllServicesAndCharacteristics();, but you never use any result from it. Why is that?
CertainPerformance highlighted in my previous post advised me to avoid the explicit Promise construction antipattern with reference to to following question in stackoverflow
Frankly, Speaking, I am new to JS and node and I haven't used promise a lot. I went and read those article but either I was unable to comprehend or unable to relate or maybe somewhere my understanding of promises have been vague/wrong all together
So I decided to ask this question in a new thread and seek for help.
So what am I doing and why am I doing it
I am creating helper/common function which I could use to keep my code tidy and if in case I want to change anything inside function at anytime, I don't have to manually change every function.
So these are the functions I have made
//Find user by email Address
const findUserByEmail = (emailAddress) => {
return new Promise((resolve, reject) => {
User.findOne({email: emailAddress}).then(response => {
resolve(res)
}).catch(error => {
reject("Error in findUserByEmail", error);
})
})
}
//Create User
const createNewUser = (newUserDetails) => {
return new Promise((resolve, reject) => {
new User({
fullName: newUserDetails.fullName,
email: newUserDetails.email,
image: newUserDetails.image,
gender: newUserDetails.gender,
age: newUserDetails.age
}).save().then((response) => {
resolve(response)
}).catch((error) => {
reject("Problem in Creating New User", error)
})
})
}
Question 1
Now, I am assuming CertainPerformance said the excessive use of promises because I am creating new promise return new Promise((resolve, reject) => { when I am already using promises with mongoose User.findOne({email: emailAddress}).then(response => { ?
But the reason for me to create those promise was, when I call these helper function from anywhere in my app after importing
const { findUserByEmail } = require("./my_db_query");
I would probably want it return a response or throw an error in case of error
findUserByEmail("test#example.com").then(/*...*/).catch(/*...*/);
If I change my above code snippet without adding new promise
function findUserByEmail (email) {
return User.findOne({email: email}).then(currentUser => currentUser).catch(error => error)
}
Question 2
Then I won't probably be able to .then and .catch in findUserByEmail("test#example.com")?
And In API route of App, where I would be calling the findUserByEmail("test#example.com") function, I would want to do something else if there is an error (which would be different for different case and hence I cannot use it in my helper function).
Question 3
Does, it make sense now for doing return new Promise((resolve, reject) => { instead of doing just one return User.findOne( or am I missing something?
Because .findOne already returns a Promise, there's no need to construct a new one with new Promise - instead, just chain onto the existing Promise chain with .then and .catch as needed. Such Promise chains can have any number of .thens and .catchs - just because you consume a Promise with one .then doesn't prevent you from using the same resolve value elsewhere. To illustrate:
makePromise()
.then((result) => {
console.log(result);
// Returning inside a `.then` will pass along the value to the next `.then`:
return result;
})
.then((result) => {
// this `result` will be the same as the one above
});
In other words - there's no need to construct a new Promise every time you want to be able to use another .then. So:
Then I won't probably be able to .then and .catch in findUserByEmail("test#example.com")
isn't correct - you can indeed chain onto the end of an existing Promise with as many .thens and .catches as you want.
Note that a .then which only returns its parameter and does nothing else (such as .then(currentUser => currentUser)) is superfluous - it won't do anything at all. Also note that a .catch will catch Promise rejections and resolve to a resolved Promise. So if you do
function findUserByEmail(email) {
return User.findOne({email: email})
.then(currentUser => currentUser)
.catch(error => error)
}
that catch means that callers of findUserByEmail will not be able to catch errors, because any possible errors were caught in findUserByEmail's catch. Usually, it's a good idea to allow errors to percolate up to the caller of the function, that way you could, for example:
someFunctionThatReturnsPromise('foobar')
.then((result) => {
// everything is normal, send the result
res.send(result);
})
.catch((err) => {
// there was an error, set response status code to 500:
res.status(500).send('there was an error');
})
So, unless your findUserByEmail or createNewUser helper functions need to do something specific when there's an error, it would probably be best just to return the Promise alone:
const findUserByEmail = email => User.findOne(email);
const createNewUser = newUserDetails => new User(newUserDetails).save();
If your helper functions do need to do something when there's an error, then to make sure that the error gets passed along properly to the caller of the function, I'd recommend either throwing the error inside the catch:
const findUserByEmail = email => User.findOne(email)
.catch((err) => {
// error handling - save error text somewhere, do a console.log, etc
throw err;
});
so that you can catch when something else calls findUserByEmail. Otherwise, if you do something like
const findUserByEmail = email => User.findOne(email)
.catch((err) => {
// do something with err
return err;
});
then the caller of findUserByEmail will have to check inside the .then if the result is actually an error, which is weird:
findUserByEmail('foo#bar.com')
.then((result) => {
if (result instanceof Error) {
// do something
} else {
// No errors
}
});
Better to throw the error in findUserByEmail's catch, so that the consumer of findUserByEmail can also .catch.
It never makes sense to create a promise with promise constructor when there's existing promise, that's why it's called promise construction antipattern.
This is a mistake, reject("Error in findUserByEmail", error). reject accepts only 1
argument, which is rejection reason. error will be ignored. It's conventionanl for an error to be Error object and not a string.
The function may be refactored to:
const findUserByEmail = (emailAddress) => {
return User.findOne({email: emailAddress})
.then(response => response) // noop
.catch(error => {
const readableError = new Error('Error in findUserByEmail');
readableError.originalError = error;
throw readableError;
});
})
}
etc.
Antipatterns don't necessary result in bad performance but they result in code smell. They make the code harder to read, maintain and test, also show that a developer may have a poor understanding of the subject.
Promise constructor has some insignificant performance impact. It introduces another level of nesting and contributes to callback hell - promises are supposed to help avoiding it.
If I change my above code snippet without adding new promise <...>
Then I won't probably be able to .then and .catch in findUserByEmail("test#example.com")?
No, a promise can be chained with then(...) and catch(...) (which is syntactic sugar for then(null, ...)) as many times as needed, that's the strong side of the pattern. Notice that catch(err => { return err }) and catch(err => { throw err }) is not the same thing, the former catches an error, the latter rethrows it.
I'm working on a new framework of microservices built in Node 8 and trying to simplify some of the logic required for passing Promises around between services.
I have a function I import in each service called StandardPromise which you can pass a Promise to. StandardPromise will call .then() on the promise and place the result in an object. If the promise was resolved it will be placed in the data attribute, if was rejected or threw an error then that will go in the err attribute.
The result of the above is that when a service receives a standardized promise by awaiting a call to another service, it can just check if there's anything in err and move forward with data if err is empty. This flow is significantly simpler than having .then() and .catch() blocks in every function.
I'm pretty happy with how it's turning out, and it seems to be working great, but since I haven't seen many examples of this kind of flow I want to know if there's something I'm missing that makes this a terrible idea or an antipattern or anything like that.
Here's a simplified, somewhat pseudocode example:
Service1:
const sp = require('./standardPromise');
const rp = require('request-promise-native');
function ex() {
// Wrap the Promise returned from rp as a "standardPromise"
return sp(rp.get({url: 'https://example.com'}));
}
Service2:
const Service1 = require('./Service1');
async function ex2() {
var res = await Service1.ex();
if (res.err) {
// Do error stuff
console.error(res.err);
return;
}
// Here we know res.data is our clean data
// Do whatever with res.data
return res.data;
}
standardPromise:
module.exports = function(promise) {
try {
return promise.then((data) => {
return {err: undefined, data: data};
}).catch((err) => {
return Promise.resolve({err: err, data: undefined});
});
} catch(err) {
console.error('promise_resolution_error', err);
return Promise.resolve({err: err, data: undefined});
}
}
It can just check if there's anything in err and move forward with data if err is empty. This flow is significantly simpler than having .then() and .catch() blocks in every function.
No, this is much more complicated, as you always have to check for your err. The point of promises is to not have .catch() blocks in every function, as most functions do not deal with errors. This is a significant advantage over the old nodeback pattern.
You would drop your standardPromise stuff and just write
// Service1:
const rp = require('request-promise-native');
function ex() {
return rp.get({url: 'https://example.com'});
}
// Service2:
const Service1 = require('./Service1');
async function ex2() {
try {
var data = await Service1.ex();
} catch(err) {
// Do error stuff
console.error(err);
return;
}
// Here we know data is our clean data
// Do whatever with data
return data;
}
or actually simpler with then for handling errors:
// Service2:
const Service1 = require('./Service1');
function ex2() {
return Service1.ex().then(data => {
// Here we know data is our clean data
// Do whatever with data
return data;
}, err => {
// Do error stuff
console.error(err);
});
}
I have an action which creates a new Promise and return a resolve:
actions: {
myAction(context, data) {
return new Promise((resolve, reject) => {
this.$http("/api/something").then(response => {
resolve(response);
}, error => {
reject(error);
})
})
}
}
Now, I have two components calling this action (generating two new Promises), but only the second function needs to do another action after the resolve arrives.
firstCall: function() {
this.$store.dispatch("myAction");
}
secondCall: function() {
this.$store.dispatch("myAction").then(response => {
//Do something after receiving new data
}, error => {
console.error("Error")
})
}
Is this a mistake/bad practice to generate a Promise without responding to all of its resolves?
Is this a mistake/bad practice to generate a Promise without responding to all of its resolves?
It's fine not to necessarily process resolutions, but not processing rejections is generally poor practice (and in fact, now generates warnings from up-to-date browsers; and NodeJS will soon be updated [unless it already has been] to terminate its process on an unhandled rejection).
So you want to be sure you catch any errors:
firstCall: function() {
this.$store.dispatch("myAction")
.catch(error => /* something here */);
}
(secondCall is already doing that, with the second argument to then.)
Unrelated, but that code exhibits the promise-creation-antipattern. You don't need a new promise, you already have one. Just:
actions: {
myAction(context, data) {
return this.$http("/api/something");
}
}
That does exactly what your code does, but more efficiently. Even if you're doing something in those then handlers that you've removed for the purposes of the question, since then returns a new promise, you wouldn't need new Promise.
I have a function chain in a node 4.3 script that looks something like, callback -> promise -> async/await -> async/await -> async/await
like so:
const topLevel = (resolve, reject) => {
const foo = doThing(data)
.then(results => {
resolve(results)
})
.catch(err => {
reject(err)
})
}
async function doThing(data) {
const thing = await doAnotherThing(data)
return thing
}
async function doAnotherThing(data) {
const thingDone = await etcFunction(data)
return thingDone
}
(The reason it isn't async/await all the way through is that the top level function is a task queue library, and ostensibly can't be run async/await style)
If etcFunction() throws, does the error bubble up all the way to the top-level Promise?
If not, how can I bubble-up errors? Do I need to wrap each await in a try/catch and throw from there, like so?
async function doAnotherThing(data) {
try {
await etcFunction(data)
} catch(err) {
throw err
}
}
If etcFunction() throws, does the error bubble up all the way through the async functions?
Yes. The promise returned by the outermost function will be rejected. There's no need to do try { … } catch(e) { throw e; }, that's just as pointless as it would be in synchronous code.
… bubble up all the way to the top-level Promise?
No. Your topLevel contains multiple mistakes. If you don't return the doThing(data) from the then callback, it will be ignored (not even awaited) and the rejection stays unhandled. You'll have to use
.then(data => { return doThing(data); })
// or
.then(data => doThing(data))
// or just
.then(doThing) // recommended
And in general, your function should look like this:
function toplevel(onsuccess, onerror) {
makePromise()
.then(doThing)
.then(onsuccess, onerror);
}
No unnecessary function expressions, no .then(…).catch(…) antipattern (that could lead to onsuccess and onerror to both be called).
I know this question is old, but as your question is written, wouldn't the doAnotherThing() function be unnecessary because it just wraps etcFunction()?
So your code could be simplified to this:
async function topLevel(){
let thing = await doThing(data)
let thingDone = await etcFunction(data)
//Everything is done here...
}
//Everything starts here...
topLevel()
I just had a similar issue where my nested errors didn't appear to bubble up to my top level function.
The fix for me was to remove the "try / catch" from my nested function and allow the error to just be thrown.