promise.all to use getDownloadURL() in firebase - javascript

I'm struggling to fetch the download image URL in firebase storage.
What I wanted to achieve with this code is to upload two images one after another and to push the image URL to the array.
However, my code doesn't work correctly as asynchronous.
Ther result after Promise.all() suppose to return the array with URL.
I'll appreciate if someone guides me to solve this issue.
const handleUpload = () => {
Promise.all([uploadImage(window1Image), uploadImage(window2Image)])
.then((result) => {
console.log(result);
})
.catch((err) => {
console.log(err);
});
};
const uploadImage = (image) => {
const uuid = Date.now() + uuidv4();
const imageToServer = storage.ref(`images/${uuid}`).put(image);
imageToServer.on(
'state_changed',
(snapshot) => {
const progress = Math.round((snapshot.bytesTransferred / snapshot.totalBytes) * 100);
},
(error) => {
console.log(error);
},
() => {
storage
.ref('images')
.child(uuid)
.getDownloadURL()
.then((data) => data);
},
);
};

You should use the then() method of UploadTask which "behaves like a Promise, and resolves with its snapshot data when the upload completes".
The following should work (untested):
const handleUpload = () => {
Promise.all([uploadImage(window1Image), uploadImage(window2Image)])
.then(result => {
console.log(result);
})
.catch(err => {
console.log(err);
});
};
const uploadImage = image => {
const uuid = Date.now() + uuidv4();
const imageToServer = storage.ref(`images/${uuid}`).put(image);
return imageToServer.then(uploadTaskSnapshot => {
return uploadTaskSnapshot.ref.getDownloadURL();
});
};

Related

Wait for a Javascript function to finish executing and fetch the response from it

I've a array of images and I am uploading these images to firebase storage.
data = {
...data,
downloadedUrl: [],
};
if (data?.image?.length) {
for (const image of data?.image) {
await uploadFile(image, data);
}
}
uploadFile handles the logic for uploading the image to firebase.
const uploadFile = useCallback((file, data) => {
if (!file) return;
const storageRef = ref(storage, `/images/${file.name}`);
const uploadTask = uploadBytesResumable(storageRef, file);
uploadTask.on(
"state_changed",
(snap_shot) => {},
(err) => console.log(err),
async () => {
await getDownloadURL(uploadTask.snapshot.ref).then((url) => {
data.downloadedUrl.push(url);
});
}
);
}, []);
It takes few seconds to get the downloadedUrl from uploadTask and I want to store this downloadedUrl in firebase firestore when I get all the urls.
Issue with the current approach is that before getting the urls, the other function start executing and I am not able to upload this data on firestore with the downloadedUrl
Here's the full function when someone clicks on form submit
const handleFormSubmit = useCallback(
async (data) => {
setLoading(true);
data = {
...data,
downloadedUrl: [],
};
if (data?.image?.length) {
for (const image of data?.image) {
await uploadFile(image, data);
}
}
if (data.downloadedUrl.length) {
uploadDataToFirestore(data);
}
if (!data.downloadedUrl?.length) {
dispatch(handleAlertState({ message: "Error Occured!!!" }));
router.push("/services");
return;
}
setLoading(false);
router.push("/");
},
[dispatch, router, uploadDataToFirestore, uploadFile]
);
const uploadDataToFirestore = useCallback(
async (data) => {
await setDoc(doc(db, "form-responses"), data)
.then((response) => {
console.log("response", response);
dispatch(
handleAlertState({
message: "Success. Your request has been sent. Thank You.",
})
);
})
.catch((error) => {
console.log("error", error);
});
},
[dispatch]
);
This bellow block of code executes the code while images are being uploaded to the cloud storage.
I want to wait for the downloadedUrl and then upload the urls to firebase firestore.
if (!data.downloadedUrl?.length) {
dispatch(handleAlertState({ message: "Error Occured!!!" }));
router.push("/services");
return;
}
Create array of promises
Use Promise.all to watch for every promise
const uploadFile = useCallback((file, data) => {
return new Promise((resolve, reject) => {
if (!file) reject();
const storageRef = ref(storage, `/images/${file.name}`);
const uploadTask = uploadBytesResumable(storageRef, file);
uploadTask.on(
'state_changed',
snap_shot => {},
err => reject(err),
() => resolve(getDownloadURL(uploadTask.snapshot.ref)),
);
});
}, []);
let allPromises = [];
if (data?.image?.length) {
for (const image of data?.image) {
allPromises.push(uploadFile(image, data));
}
}
let uploadedUrls = await Promise.all(allPromises);
console.log(uploadedUrls);
await Promise.all take an array of promises, we created a new array which holds the promise returned from uploadFile function. when all promises resolved then promise.all resolved as well and return array of urls. we await for Promise.all so it will not execute next line until resolved

creating asynchronous function with a loop inside

I have a little problem, I need the makeZip function to wait for the takeScreenshot function to take all the screenshots it needs, how do I do this while taking care of best practices?
(I know at this point "then" doesn't make sense with the post method, I just tried it my way before but it didn't work the way I wanted)
Function:
const takeScreenshot = (url) => {
const resolutionsArray = Object.values(resolutions);
resolutionsArray.map(async (mediaSize) => {
webshot(url, setFileName(url, mediaSize), setOptions(mediaSize), (err) => {
if (!err) {
console.log("screenshot taken!");
}
});
});
};
calling functions:
app.post("/", async (req, res) => {
const { url } = req.body;
takeScreenshot(url)
.then((url) => makeZip(url))
.then((url) => sendEmail(url))
.then((message) => res.send(message))
.catch((err) => console.log(err));
});
My suggestion is:
to use Promise.all or Promise.allSettled when you need to handle several promises
extract callback of map fn
const makeWebshot = (argsHere) => new Promise((reselove, reject) => {
webshot(url, setFileName(url, mediaSize), setOptions(mediaSize), (err) => {
if (err) return reject(err);
return resolve();
});
});
Update takeScreenshot to
const takeScreenshot = (url) => {
const resolutionsArray = Object.values(resolutions);
return Promise.all(resolutionsArray.map((mediaSize) => makeWebshot(argsHere)));
};
When dealing with a list of Promises you will want to use Promise.all to wait for them all to resolve. Here is a simple example:
const list = [1,2,3];
const all = list.map(i => new Promise((resolve, reject) => {
setTimeout(() => {
console.log(i);
resolve(i*2);
}, 100);
}));
Promise.all(all).then(console.log)
In your case it would be something like this:
const takeScreenshot = (url) =>
Object.values(resolutions).map(async (mediaSize) => {
webshot(url, setFileName(url, mediaSize), setOptions(mediaSize), (err) => {
if (!err) {
console.log("screenshot taken!");
}
});
});
app.post("/", async (req, res) => {
const { url } = req.body;
Promise.all(takeScreenshot(url))
.then((listOfUrls) => ...
});
But since I don't know what webshot returns, I can't tell you what the processing of the listOfUrls should look like.

await not waiting for async function to return value

I'm using react with firebase where I need to upload some pictures to firebase storage and then save the download url that gets returned from the upload function to store that value on firestore.
This is my image uploading function
const imageUpload = async (image) => {
const uploadTask = storage.ref(`images/${image.name}`).put(image);
uploadTask.on(
'state_changed',
(snapshot) => {},
(error) => {
console.log(error);
},
() => {
storage
.ref('images')
.child(image.name)
.getDownloadURL()
.then((url) => {
setImageUrl(url);
console.log(url);
return url;
});
}
);
};
And this is my on submit handler
const handleSubmit = async (e) => {
e.preventDefault();
let entry = {
author: currentUser.email,
body,
};
if (image) {
await imageUpload(image).then(async (url) => {
console.log(url);
entry = {
author: currentUser.email,
body,
imageUrl,
};
try {
await createEntry(entry).then(() => {
setBody('');
setShowSnackbar(true);
});
} catch (error) {
console.log(error);
}
});
}
try {
await createEntry(entry).then(() => {
setBody('');
setShowSnackbar(true);
});
} catch (error) {
console.log(error);
}
};
However this doesn't work because the console shows undefined first and then the url which means that the await is not waiting for the url to be returned. How do i resolve this?
I think you are mixing things.
You don't need to use then on your promises if you are using async / await
Using the async/await idiom your code should look more like
async function handleSubmit(e) {
e.preventDefault();
let entry = {
author: currentUser.email,
body,
};
if (image) {
const url = await imageUpload(image);
entry = {
author: currentUser.email,
body,
imageUrl,
};
try {
await createEntry(entry);
setBody("");
setShowSnackbar(true);
} catch (error) {
console.log(error);
}
}
try {
await createEntry(entry);
setBody("");
setShowSnackbar(true);
} catch (error) {
console.log(error);
}
}
async function imageUpload(image) {
const uploadTask = storage.ref(`images/${image.name}`).put(image);
return new Promise((resolve, reject) => {
uploadTask.on(
"state_changed",
(snapshot) => {},
(error) => {
reject(error);
},
() => {
storage
.ref("images")
.child(image.name)
.getDownloadURL()
.then((url) => {
setImageUrl(url);
resolve(url);
});
}
);
});
}
async/await is actually intended to make programming with promises somehow 'feel' synchronous. Using then and callbacks, beside the fact that the code would not work, makes it taking no benefit from the syntax.
see https://developer.mozilla.org/fr/docs/Learn/JavaScript/Asynchronous/Concepts
The issue is primarily because of await and then are used together.
By converting your code to use await only can help.
Imagine a scenario where a function c is called when function a call to an asynchronous function b resolves:
const a = () => {
b().then(() => c());
};
Here’s the same program, written using async/await instead of promise:
const a = async () => {
await b();
c();
};
So your logic of image upload can look like code below and you can convert rest of the code:
const url = await imageUpload(image)
console.log(url);
entry = {
author: currentUser.email,
body,
imageUrl,
};
and the imageUpload function can look like,
async function imageUpload(image) {
try {
const storageRef = firebase.storage().ref();
// Create the file metadata
const metadata = { contentType: "image/jpeg" };
const fileRef = storageRef.child(`${this.name}/` + image.name);
const uploadTaskSnapshot = await fileRef.put(file, metadata);
const downloadURL = await uploadTaskSnapshot.ref.getDownloadURL();
setImageUrl(url);
console.log(url);
return downloadURL;
} catch (error) {
console.log("ERR ===", error);
}
}

How do you pull a list of images from firebase storage?

I've managed to successfully upload some photos to firebase but I want to download all the images at a certain location
currently I have to do this ugly code:
const getPhotos = async () => {
var storage = firebaseApp.storage()
var gsReference = storage.refFromURL('gs://my-app.appspot.com/')
for (let i = 1; i < 4; i++) {
await gsReference
.child(`${user.id}/${name}/photo${i}.jpg`)
.getDownloadURL()
.then((url) => {
fetch(url).then((res) => {
setPhotos((photos) => photos.concat(res.url))
})
})
.catch((error) => {
console.log('ERROR!', error)
})
}
}
this relies on all the photos being called photo1.jpg, photo2.jpg etc.
is there a way to just pull them all regardless of what they're called?
Apparently there is Reference.listAll function
const getPhotos = async () => {
const storage = firebaseApp.storage();
let reference = storage.refFromURL('gs://my-app.appspot.com/');
reference = reference.child(`${user.id}/${name}`);
const {items} = await reference.listAll();
const urlPromises = items.map(pr => pr.getDownloadURL()
.then(url => fetch(url))
.catch(error => error));
let urls = await Promise.all(urlPromises);
urls = urls.filter(result => !(result instanceof Error));
setPhotos(urls);
}

Multiple API calls with Promise.all and dispatch an action

I want to call multiple API's and store each response data in an object then I want to dispatch this response object but I'm getting undefined.
Below is the code I tried. May I know where I'm doing wrong?
/* COMPONENT.JSX */
componentDidMount() {
callApis(this.props.products, this.props.profileId);
}
/* API.JS */
const getContactDetails = (http, profileId) =>
(http.get(`https://www.fakeurl.com/${profileId}/contact`));
const getProductDetails = (http, profileId) =>
(http.get(`https://www.fakeurl.com/${profileId}/product`));
const callApis = (products, profileId) => (dispatch) => {
const payload = new Map();
products.forEach((product) => {
const apis = [getContactDetails, getProductDetails];
apis.map(api => api(http, profileId));
Promise.all(apis)
.then((response) => {
const apiData = {
contactData: getParsedContactData(response[0]),
productData: getParsedProductData(response[1])
};
if (payload.get(product.token)) {
payload.get(companion.token).push(apiData);
} else {
payload.set(product.token, [apiData]);
}
})
.catch(err => {
throw ('An error occurred ', err);
});
});
dispatch({ type: FETCH_API_DATA, payload: payload });
}
I expect the dispatch will be called after all API's were resolved, get parsed, and map into the payload object then it should dispatch.
Array.map returns a new Array, which you are discarding
you're calling dispatch before any of the asynchronous code has run
A few minor changes are required
/* API.JS */
const getContactDetails = (http, profileId) => http.get(`https://www.fakeurl.com/${profileId}/contact`);
const getProductDetails = (http, profileId) => http.get(`https://www.fakeurl.com/${profileId}/product`);
const callApis = (products, profileId) => (dispatch) => {
const payload = new Map();
// *** 1
const outerPromises = products.map((product) => {
const apis = [getContactDetails, getProductDetails];
// *** 2
const promises = apis.map(api => api(http, profileId));
// *** 3
return Promise.all(promises)
.then((response) => {
const apiData = {
contactData: getParsedContactData(response[0]),
productData: getParsedProductData(response[1])
};
if (payload.get(product.token)) {
payload.get(companion.token).push(apiData);
} else {
payload.set(product.token, [apiData]);
}
})
.catch(err => {
throw ('An error occurred ', err);
});
}));
// *** 4
Promise.all(outerPromises)
.then(() => dispatch({
type: FETCH_API_DATA,
payload: payload
})
)
.catch(err => console.log(err));
}
rather than procucts.forEach, use products.map
capture the promises in apis.map to use in Promise.all
return Promise.all so the outer Promises can be waited for
Promise.all on the outer promises, to wait for everything to complete.
const callApis = (products, profileId) => async (dispatch) => { // use async function
const payload = new Map();
for (const product of products) {
const apis = [getContactDetails, getProductDetails];
apis.map(api => api(http, profileId));
await Promise.all(apis) // await all promise done
.then((response) => {
const apiData = {
contactData: getParsedContactData(response[0]),
productData: getParsedProductData(response[1])
};
if (payload.get(product.token)) {
payload.get(companion.token).push(apiData);
} else {
payload.set(product.token, [apiData]);
}
})
.catch(err => {
throw ('An error occurred ', err);
});
}
dispatch({ type: FETCH_API_DATA, payload: payload }); // dispatch will be executed when all promise done
}

Categories

Resources