I have the following code which fetches some data by implementing promises.
var TreeDataService = new DataService();
export default class TreeStore {
treeData = [];
getData() {
TreeDataService.get().then(data => {
this.treeData = data;
},
() => {
alert("Error fetching data");
});
return this.treeData; //this returns empty array instead of returning the data fetched from TreeDataService.get
}
}
How can I make return this.treeData execute only after the promise is fully resolved? I know I can put return this.treeData inside then's success method and return the entire getDatamethod as a promise, but that will require again resolving the promise at the call site of getData.
EDIT: I understand that as it's a async operation, I cannot synchronously execute the return statement and can instead return a promise. But then how do I resolve that promise at call site? I am facing the same issue at calling code:
export default class App extends React.Component {
treeData = TreeStoreObj.getData().then(data => {
return data;
}); // This will also execute asynchronously, so will be initially empty.
render() {
return (
<div className="app-container">
<TreeNode node={this.treeData} /> // So this will also be empty
</div>
);
}
}
Earlier code will now be:
export default class TreeStore {
treeData = [];
return getData() {
TreeDataService.get().then(data => {
return this.treeData = data;
},
() => {
alert("Error fetching data");
});
}
}
You are doing something strange. getData should not return any value at all if you are using mobx. It should set store observable only. React component will automatically rerender when this observable value change.
Are you using mobx-react? If so show your integration code. If not try to use it ;)
Async model does not allow you to execute return this.treeData after the promise is resolved. However, you might want to cache the promise not to invoke TreeDataService.get() several times.
For example (untested, just trying to show the main idea):
export default class TreeStore {
treeData = [];
treeDataPromise = null;
getData() {
if (this.treeDataPromise) return this.treeDataPromise;
this.treeDataPromise = TreeDataService.get().then(data => {
this.treeData = data;
return this.treeData;
},
() => {
alert("Error fetching data");
});
return this.treeDataPromise;
}
}
Another option is to check if this.treeData is loaded, and return Promise.resolve(this.treeData) if this is the case.
Related
I have created an endpoint in express that handles get requests. From a react component, I make a get request to said endpoint using axios. I want to store the data in an object in my Component class so that it can be accessed at multiple times (onComponentDidLoad, multiple onClick event handlers, etc). Is there a way to store the data outside of the axios promise, and/or preserve the promise so that I can do multiple .then calls without the promise being fulfilled?
I have tried using setState(), returning the promise, and returning the actual data from the get request.
Here is what I have right now:
constructor {
super();
this.myData = [];
this.getData = this.getData.bind(this);
this.storeData = this.storeData.bind(this);
this.showData = this.showData.bind(this);
}
// Store data
storeData = (data) => {this.myData.push(data)};
// Get data from API
getData() {
axios
.get('/someEndpoint')
.then(response => {
let body = response['data'];
if(body) {
this.storeData(body);
}
})
.catch(function(error) {
console.log(error);
});
}
showData() {
console.log(this.myData.length); // Always results in '0'
}
componentDidMount = () => {
this.getData(); // Get data
this.showData(); // Show Data
}
render() {
return(
<Button onClick={this.showData}> Show Data </Button>
);
}
Edit
I was incorrect in my question, storing the promise and then making multiple .then calls works. I had it formatted wrong when i tried it.
This code won't quite work because you're attempting to show the data without waiting it to be resolved:
componentDidMount = () => {
this.getData();
this.showData();
}
As you hinted toward in your original post, you'll need to extract the data from the Promise and there's no way to do that in a synchronous manner. The first thing you can do is simply store the original Promise and access it when required - Promises can be then()ed multiple times:
class C extends React.Component {
state = {
promise: Promise.reject("not yet ready")
};
showData = async () => {
// You can now re-use this.state.promise.
// The caveat here is that you might potentially wait forever for a promise to resolve.
console.log(await this.state.promise);
}
componentDidMount() {
const t = fetchData();
this.setState({ promise: t });
// Take care not to re-assign here this.state.promise here, as otherwise
// subsequent calls to t.then() will have the return value of showData() (undefined)
// instead of the data you want.
t.then(() => this.showData());
}
render() {
const handleClick = () => {
this.showData();
};
return <button onClick={handleClick}>Click Me</button>;
}
}
Another approach would be to try to keep your component as synchronous as possible by limiting the asyncrony entirely to the fetchData() function, which may make your component a little easier to reason about:
class C extends React.Component {
state = {
status: "pending",
data: undefined
};
async fetchData(abortSignal) {
this.setState({ status: "pending" });
try {
const response = await fetch(..., { signal: abortSignal });
const data = await response.json();
this.setState({ data: data, status: "ok" });
} catch (err) {
this.setState({ error: err, status: "error" });
} finally {
this.setState({ status: "pending" });
}
}
showData() {
// Note how we now do not need to pollute showData() with asyncrony
switch (this.state.status) {
case "pending":
...
case "ok":
console.log(this.state.data);
case "error":
...
}
}
componentDidMount() {
// Using an instance property is analogous to using a ref in React Hooks.
// We don't want this to be state because we don't want the component to update when the abort controller changes.
this.abortCtrl = new AbortController();
this.fetchData(this.abortCtrl.signal);
}
componentDidUnmount() {
this.abortCtrl.abort();
}
render() {
return <button onClick={() => this.showData()}>Click Me</button>
}
}
If you just store the promise locally and access it as a promise it should work fine.
getData() {
// if request has already been made then just return the previous request.
this.data = this.data || axios.get(url)
.then( response => response.data)
.catch(console.log)
return this.data
}
showData() {
this.getData().then(d => console.log('my data is', data));
}
I have a promise that return once a correct event is called with the correct action. This is what I have so far
import {EventBus} from "./EventBus";
export function completed() {
EventBus.$on('queue-action', e => {
return new Promise((resolve,reject) => {
if(e.action == 'completed'){
let item = e.queueItem
resolve(item);
}else{
reject(new Error('No action specified in event object'))
}
})
});
}
export function emitAction(action, queueItem) {
EventBus.$emit('queue-action', {
action,
queueItem
});
}
When calling the completed function in one of my components like this
completed()
.then((item)=> console.log('promise'))
.catch((error) => console.log(error) );
it returns undefined once I add the then and catch methods to this function. It looks like the problem is with me then and catch, but I am unable to determine what it is. From what I have seen online whatever variable you use for the data you use in the then statement.
What I am trying to do is let an element in the "queue" to emit an event to the to the queue with an action for example completed. The queue should then resolve the promise to edit the queue in the intended purpose of that action or react to an error from the promise.
This is what I have done so far
import {EventBus} from "./EventBus";
export class QueueEvent {
constructor(){}
emitAction(action, queueItem){
return new Promise((resolve,reject) => {
EventBus.$emit('queue-action', {
action,
queueItem
},resolve,reject);
});
}
}
export class QueueEvents extends QueueEvent{
constructor(){
super();
}
listenForComplete() {
}
}
Your completed function is not returning a promise (it is returning undefined as you noticed).
You are returning the promise for the event emitter when the queue-action is called. You are defining a new function here: e => { and that function that is returning a promise is passed to the EventBus event emitter
You want to wrap the whole EventBus.$on() in your promise, like this:
export function completed() {
return new Promise((resolve) => {
EventBus.$on('queue-action', e => {
if(e.action == 'completed'){
let item = e.queueItem
resolve(item);
}
});
});
}
As a rule of thumb, unless you have a very specific reason to do something else, a function returning a promise should have all it's body wrapped in return new Promise(...);. It is also normal and ok to have a lot of code wrapped inside a promise.
Note to the code: I removed reject part both for brevity and because I'm not sure that is what you want to do. Unless it is an error if some action happens before 'completed', you should just ignore such an event.
This question already has answers here:
Wait until all promises complete even if some rejected
(20 answers)
Closed 5 years ago.
I am using API call to get data and update my redux store. Following is state changes for each API call.
Before making API call, set isLoading as true
On success reset isLoading as false
On failure reset isLoading as false and set isError as true.
My component needs data from three APIs which is stored in three different redux stores as below structure.
store: {
book: {
isLoading: false,
isError: false,
data: {}
},
teacher: {
isLoading: false,
isError: false,
data: {}
},
}
In my component, I use following to call api
componentWillMount() {
const {
loadBook,
loadTeacher,
} = this.props;
// all these load functions dispatch action which returns Promise for async API call using `fetch`
const apiCalls = [
loadBook(),
loadTeacher(),
];
Promise.all(apiCalls);
}
I have written selector to check the loading state as below.
export const getIsLoading = createSelector([
getIsBookLoading,
getIsTeacherLoading,
],
(bLoading, tLoading) => (
bLoading || tLoading
)
);
Based on value of getIsLoading I do show loading state in my component otherwise render component.
However I see problem happens when one of the API call fails. For example, loadBook fails in 100 ms and that time bLoading is changed back to false however tLoading still is true bcz loadTeacher api calls was not finished. Since Promise.all() do either all or nothing therefore API call for loadTeacher never finished therefore tLoading stas true.
Is there a way to let Promsie.all to resolve all the calls even if some failed so that it can clean dirty state?
If loadbook fails then loadTeacher won't stop, your resolve handler of Promise.all simply isn't called.
You can see in the following code that both tLoading (set false in resolve) and bLoading (set in catch) are false:
var bLoading = true;
var tLoading = true;
const loadBook =
() => Promise.reject("load book rejects");
const loadTeacher =
() => Promise.resolve("load book rejects")
.then(()=>tLoading=false);
Promise.all([
loadBook()
.catch(
(err)=>{
bLoading=false;
return Promise.reject(err);
}
),
loadTeacher()
])
.catch(
()=>console.log("bLoading:",bLoading,"tLoading:",tLoading)
);
Cannot insert as snipped because that is buggy as hell in Stack Overflow and keeps failing but you can run the code in the console.
Ash Kander is on the right track if you want to use Promise.all without failing but how do you distinguish between valid resolve values and rejects? It's better to make an explicit fail value:
const Fail = function(reason){this.reason=reason;};
Promise.all(
[
loadBook,
loadTeacher
].map(
fn=>
fn()
.catch(
(err)=>
new Fail(err)
)
)
)//this will never fail
.then(
([book,teacher])=>{
console.log("did book fail?",(book&&book.constructor===Fail));
console.log("did teacher fail?",(teacher&&teacher.constructor===Fail));
}
)
You have to either use a dedicated library (there are some, I dont remember the names -_-) or do it yourself - which is not so hard.
I do have some code doing it:
var MyPromise = {};
MyPromise.getDefaultPromise = function () {
return new Promise(function (resolve, reject) { resolve(); });
};
MyPromise.when = function (promises) {
var myPromises = [];
for (var i = 0; i < promises.length; i++) {
myPromises.push(MyPromise.reflect(promises[i]));
}
return Promise.all(myPromises).
then(function (oResult) {
var failure = oResult.filter(function (x) { return x.status === 'error';});
if (failure.length) {
return MyPromise.fail(failure);
}
return MyPromise.getDefaultPromise();
});
};
MyPromise.fail = function (failure) {
return new Promise(function (resolve, reject) {
reject(failure);
});
};
MyPromise.reflect = function (promise) {
return new Promise(function (resolve) {
promise.then(function (result) {
resolve(result);
}).
catch(function (error) {
resolve(error);
});
});
};
Calling MyPromise.when(-your promises array-) will ALWAYS resolve, and send you an array containing the failing promises that you can analyze in the 'then'
I have a react class and I am calling getData function from the Store
class A extends React.component {
componentDidMount(){
Store.getData(a).then(data =>{
//some data
});
}
render (){
//some data
}
}
And the function in the Store is
getData : function (a) {
return (a+b);
}
when I run I get an error
Uncaught TypeError: Store.getData(a).then is not a function
How do I rectify it ?
Function then is used to handle resolved promises. In your code function getData simply returns result of a+b (i.e. no promises are used). If you want to use then for some other operations you should update getData function so it uses promises, although in your case it is not clear why you could not use the result of function immediatelly.
Here is how you could use promise:
getData : function (a) {
return new Promise(function(resolve, reject){
resolve(a + b);
});
}
However your function getData is not asynchronous so instead you could simply use the result of function getData directly in your class constructor:
class A extends React.component {
componentDidMount() {
var yourData = Store.getData(a);
// do what you need with this data
}
// the rest of your class
}
regarding the question Passing data between controllers in Angular JS? I ran into the situation that my ProductService is executing some $http(RestFUL Service) and returns NULL because the callback function isn't completed.
Because the p.getProducts() function is evaluted and the callback function which fetches the data from the RestFUL service, is'nt complete the function returns always null.
app.service('productService', function() {
p = this
p.productList = [];
var addProduct = function(newObj) {
productList.push(newObj);
}
p.getProducts = function(){
return $http(RestFUL Service,function(data){p.productList.push(data)});
}
return {
addProduct: addProduct,
getProducts: return p.getProducts();
};
});
How can I solve this problem?
If you change your service to look more like this
return {
addProduct: addProduct,
getProducts: p.getProducts
}
then in controller you can make it work like that
app.controller('myCtrl', function ($scope, productService) {
var products = null
productService.getProducts().then(function(response){
//do something with data returned
})
})
your getProducts return $http which itself returns promise, that's why the I used then in controller
You must play with callback's. The http is an async operation, and for that reason you canĀ“t return a valid result right away. How invoke getProducts must set as parameter a function (callback) that will be invoked when http is completed - when data is available.
app.service('productService', function() {
p = this
p.productList = [];
var addProduct = function(newObj) {
productList.push(newObj);
}
p.getProducts = function(callback){
$http(RestFUL Service,function(data){
p.productList.push(data)
callback(data);//do something with data
});
}
return {
addProduct: addProduct,
getProducts: p.getProducts
};
}
//invoking
getProducts(function(products){
//do something with products
});