I falling to get the url return by getDownloadURL appended on the ImageUrls state, getDownloadURL takes a second or two to return the url and it seems like the code continues running and doesn't wait for it to return the url
I am trying to upload multiple images and then create an object in firestore that has the images urls and description
const createAlbum = () => {
addDoc(albumCollection, {
name: albumName,
category: category,
images: imageUrls,
description: description,
});
};
const HandleUpload = (files) => {
files.forEach((file) => {
const storageRef = ref(
storage,
`/files/albums/${albumName}/${file.name}`
);
const uploadTask = uploadBytesResumable(storageRef, file);
uploadTask.on(
"state_changed",
(snap) => {},
(err) => {},
() => {
getDownloadURL(uploadTask.snapshot.ref).then((url) => {
setImageUrls((prev) => [...prev, url]);
});
}
);
});
createAlbum();
};
Getting the download URL (like uploading the data, and most modern cloud APIs) is an asynchronous operation. While this call is being handled, your main code indeed continues to execute. Then when the download URL is available, your then callback is invoked with it, so you can use it.
For this reason, any code that needs the download URL needs to be inside the then callback, be called from there, or be otherwise synchronized.
The simplest fix is to move createAlbum into the then callback:
uploadTask.on(
"state_changed",
(snap) => {},
(err) => {},
() => {
getDownloadURL(uploadTask.snapshot.ref).then((url) => {
setImageUrls((prev) => [...prev, url]);
createAlbum();
});
}
);
If you only want to call createAlbum() once all uploads are completed, you can keep a counter or use Promise.all():
const HandleUpload = (files) => {
let promises = files.map((file) => {
const storageRef = ref(
storage,
`/files/albums/${albumName}/${file.name}`
);
return uploadBytesResumable(storageRef, file).then(() => {
return getDownloadURL(uploadTask.snapshot.ref);
});
});
Promise.all(promises).then((urls) => {
setImageUrls(urls)
createAlbum();
})
};
This code uses the fact that the task returned by uploadBytesResumable is also a Promise, so we can use then() to know when it's completed, and then gets the download URL.
Note that if setImageUrls is a useState hook, that operation is asynchronous too. I recommend passing the image URLs to createAlbum explicitly, rather than trying to pass it through the state, so createAlbum(urls)
Also you can use promise to error and callback to execute one time createAlbum()
like this:
uploadTask.on(
"state_changed",
(snap) => {},
(err) => {},
(),
error => {
console.log('upload error: ', error.message)
},
() => {
getDownloadURL(uploadTask.snapshot.ref).then((url) => {
setImageUrls((prev) => [...prev, url]);
createAlbum();
});
}
)
Related
I am trying to build a recipe database. I created a function for firebase that allows you to upload a photo to the storage and then get the url for that photo.
const handleUpload = async () => {
const storage = firebase.storage();
const uploadTask = storage.ref(`images/${image.name}`).put(image);
uploadTask.on(
firebase.storage.TaskEvent.STATE_CHANGED,
(snapshot) => {},
(error) => {
console.log(error);
},
() => {
storage
.ref("images")
.child(image.name)
.getDownloadURL()
.then((url) => {
setImageUrl(url);
console.log(url);
});
}
);
};
Then I have a form where on the onSubmit, the photo upload function is supposed to fire so it can set the URL in the state, then fire the full upload which uploads the whole recipe to firebase with the url included. It is being handled inconsistently where sometimes it works and sometimes it doesn't. I have been trying different ways to make it work, but nothing seems to make it work perfectly. Here is the current onSubmit function:
onSubmit={async (values) => {
await handleUpload().then(() => {
createRecipe(values, imageUrl)
})
}}
Any help is much appreciated!
React updates state async. You code relies on the fact that the state update setImageUrl(url) will be handled before createRecipe is called.
But there is no guarantee for that. That's basically a race between reacts internal dealings with state update, and your chain of async function.
Better to make handleUpload return a promise to the URL.
const handleUpload = async () => {
return new Promise((resolve, reject) => {
const storage = firebase.storage();
const uploadTask = storage.ref(`images/${image.name}`).put(image);
uploadTask.on(
firebase.storage.TaskEvent.STATE_CHANGED,
(snapshot) => {},
(error) => {
reject(error);
},
() => {
storage
.ref("images")
.child(image.name)
.getDownloadURL()
.then((url) => {
resolve(url);
});
}
);
}
};
This way handleUpload will actually return the url when you await it.
onSubmit={async (values) => {
await handleUpload().then((url) => {
createRecipe(values, url)
})
}}
Also note that mixing async/await and then/catch/finally is considered bad practice.
I strongly recommend going with async/await only when you can.
onSubmit={async (values) => {
try {
const url = await handleUpload()
createRecipe(values, url)
} catch (err) {
console.log(err)
}
}}
As described in comments between my code snippet, the asynchronicity is not working as expected. For each id, an object/item should return but it only returns one item since my async await isn't implemented properly. What could be a possible workaround?
Thanks in advance
useEffect(() => {
axios.get('url-here').then((res) => {
res.data.favProperties?.map((el) => {
console.log(el) // this returns multitple id's of saved/liked items
axios.get('url-here').then(async (r) => {
if (r.data) {
console.log(r.data) // Problem starts here
// This returns the full object of the liked items
// But only one object is returned, not every object for which an id was stored
await storageRef
.child(r.data.firebaseRef + '/' + r.data.images[0])
.getDownloadURL()
.then((url) => {
// Here i need to fetch the image for each object
console.log(url)
})
.catch((err) => console.log(err))
}
})
})
})
}, [])
I think breaking down your operations into functions will prevent this Promise Hell. I would recommend using async await for these kinda operations. Also I was confused about the last part of console logging the download URL, by my guess you're trying to save all the download URLs for these liked items in an array.
useEffect(() => {
firstFunction();
}, []);
const firstFunction = async () => {
const { data } = await axios.get("url-here");
const favProperties = data.favProperties;
const fetchedUrls = await Promise.all(
favProperties?.map(async (el) => (
await secondFunction(el.id) /** use el to pass some ID */
))
);
};
const secondFunction = async (someId) => {
/** your second URL must point to some ID (or some parameters) specific API otherwise
running same op in a loop without variations doesn't make any sense */
const { data } = await axios.get(`some-other-url/${someId}`);
if (data) {
console.log(data);
const fetchedUrl = await storageThing(data);
return fetchedUrl;
}
};
const storageThing = async ({ firebaseRef, images }) => {
try {
const downloadURL = await storageRef
.child(firebaseRef + "/" + images[0])
.getDownloadURL();
console.log(downloadURL);
return downloadURL;
} catch (error) {
console.log(error);
return '';
}
};
I am using a promise based hook in a React app to fetch async data from an API.
I am also using a Axios, a promise based http client to call the API.
Is it an anti-pattern to use a promise based client inside another promise? The below code does not seem to work.
const getData = () => {
return new Promise((resolve, reject) => {
const url = "/getData";
axios.get(url)
.then(function(response) {
resolve(response);
})
.catch(function(error) {
reject(error);
});
});
const useAsync = (asyncFunction) => {
const [value, setValue] = useState(null);
const execute = useCallback(() => {
setPending(true);
setValue(null);
setError(null);
return asyncFunction()
.then(response => setValue(response))
.catch(error => setError(error))
.finally(() => setPending(false));
}, [asyncFunction]);
useEffect(() => {
execute();
}, [execute]);
return { execute, pending, value, error };
};
};
const RidesList = () => {
const {
pending,
value,
error,
} = useAsync(getData);
Oh man. I think you have a fundamental misunderstanding about how Promises work.
First, axios already returns a Promise by default. So your whole first function of getData can be reduced to:
const getData = () => {
const url = "/getData"
return axios.get(url)
}
But the meat of your code seems to indicate you want a querable Promise - so you can check the status of it for whatever reason. Here's an example of how you would do it, adapted from this snippet:
function statusPromiseMaker(promise) {
if (promise.isResolved) return promise
let status = {
pending: true,
rejected: false,
fulfilled: false
}
let result = promise.then(
resolvedValue => {
status.fulfilled = true
return resolvedValue
},
rejectedError => {
status.rejected = true
throw rejectedError
}
)
.finally(() => {
status.pending = false
})
result.status = () => status
return result
}
In this way, you can then do something like let thing = statusPromiseMaker(getData()) and if you look up thing.status.pending you'll get true or false etc...
I didn't actually run what's above, I may have forgotten a bracket or two, but hopefully this helps.
I have to admit - I haven't seen anything like this ever used in the wild. I am interested in knowing what you're actually trying to accomplish by this.
Axios itself returns a promise but if you want to make a custom class having your custom logic after each API call then you can use interceptors I was having the same requirement and this is how I am returning promises after applying my custom logic on each API call.
Interceptors will get executed separately after and before each request you made so we can simply use them if we want to modify our request or response.
here is my working solution have a look at it.
callApi = (method, endpoint, params) => {
this.apiHandler.interceptors.request.use((config) => {
config.method = method
config.url = config.baseURL + endpoint
config.params = params
return config
})
return new Promise((resolve, reject) => {
this.apiHandler.interceptors.response.use((config) => {
if (config.status == 200) {
resolve(config.data)
} else {
reject(config.status)
}
// return config
}, error => reject(error))
this.apiHandler()
})
}
Below is the code to call this function
helper.callApi("get", "wo/getAllWorkOrders").then(d => {
console.log(d)
})
My question is about correctly implementing an async function to fetch data. I've a function called _getData() and I'm calling it on the componentDidMount() of a screen. But when server response is slow, switching to this screen is getting slower. So I would like to use async function for fetching data. But I'm not sure if I'm doing it correctly. Is that a correct approach? I can't be sure if it works async or not.
Here is my Api._getData() code:
const _getData = async () => {
return await axios.get("http://blabla.com/someservice", { params: someParamDataHere });
};
export const Api = {
_getData
};
and on SomeScreen.js, I also have loadData() function which calls the function above and does state updates.
loadData() {
Api._getData()
.then((response) => {
this.setState({ myData: response.data });
})
.catch((error) => {
console.log(error.response);
});
}
in componentDidMount() function of the same screen I'm calling this loadData() function directly.
Now, is it enough to declare Api._getData() as async and using await in it, or should I change some trigger functions too?
Thank you very much for your help.
instead of async await use promises
export const getRequest = (url) => {
return new Promise((resolve, reject) => {
api
.get(url)
.then((response) => {
handleReponse(response)
.then((errorFreeResponse) => {
resolve(errorFreeResponse);
})
.catch((error) => {
reject(error);
});
})
.catch((error) => {
reject(handleError(error));
});
});
};
You are doing correct while retrieving in load Data . What you can do more is try more syntactical sugar of es6 by using async await in loadData , hence
loadData = async() =>{
try{
let response = await Api._getData();
this.setState({ myData: response.data });
} catch(err){
console.log(error.response);
}
}
Hope it helps. feel free for doubts
I am updating an object in firebase using React js.
I'm using this boilerplate as reference.
updateBookList: (id, data) => {
return firebaseDb.ref('NewBooks').child(id).update(data).then(() => {
return {};
}).catch(error => {
return {
errorCode: error.code,
errorMessage: error.message
}
});
},
The following updates the Books fine.
What I want to do is return the result instead of returning a blank {}. How can I return the result of what I updated?
This is how I fetch books:
fetchBooks: () => {
return new Promise((resolve, reject) => {
const bookSub = firebaseDb.ref('NewBooks').on("value", books => {
resolve(books.val());
}, error => {
reject(error);
})
})
},
If you want to return the value, you need to retrieve it. You can do that using once and the value event, which returns a promise that resolves to a Firebase snapshot:
updateBookList: (id, data) => {
let ref = firebaseDb.ref('NewBooks');
return ref
.child(id)
.update(data)
.then(() => ref.once('value'))
.then(snapshot => snapshot.val())
.catch(error => ({
errorCode: error.code,
errorMessage: error.message
}));
}
Also, you could simplify your fetchBooks by using once there, too:
fetchBooks: () => {
return firebaseDb.ref('NewBooks')
.once("value")
.then(snapshot => snapshot.val());
}
once returns a promise, so you don't have to create your own and you won't have a dangling event listener. The call to on in your implementation of fetchBooks will see a listener added with each call and multiple calls to resolve will be attempted if the database changes.