I am struggling to force code to be synchronous. The code is intended to upload an image using a vue composable, wait for the upload to succeed, then store the url from firebase storage into a database. The best I can do is get the code to function, but the success code fires before the upload is complete (though I get the url).
The code below does not work, but it's my attempt to try to chain the actions together using then callbacks to force them to behave in a synchronous manner. Not working.
VueComponent.vue
const newImage = async () => {
if (image.value) {
await uploadImage(image.value);
} else return null;
};
const handleSubmit = async () => {
try {
const colRef = collection(db, "collection");
newImage()
.then(() => {
addDoc(colRef, {
content: content.value
});
})
.then(() => {
//code to run only on success
});
});
} catch (error) {
}
};
useStorage.js composable
import { ref } from "vue";
import { projectStorage } from "../firebase/config";
import {
uploadBytesResumable,
getDownloadURL,
ref as storageRef,
} from "#firebase/storage";
const useStorage = () => {
const error = ref(null);
const url = ref(null);
const filePath = ref(null);
const uploadImage = async (file) => {
filePath.value = `${file.name}`;
const storageReference = storageRef(projectStorage,
filePath.value);
//<--I want this to be synchronous, but it isn't.
const uploadTask = uploadBytesResumable(storageReference,
file);
uploadTask.on(
"state_changed",
(snapshot) => {
const progress =
(snapshot.bytesTransferred / snapshot.totalBytes) *
100;
console.log("Upload is " + progress + "% done");
},
(err) => {
},
() => {
getDownloadURL(uploadTask.snapshot.ref).then((downloadURL)
=>
{console.log("File available at", downloadURL);
});
}
);
};
return { url, filePath, error, uploadImage };
};
export default useStorage;
Your uploadImage doesn't wait for the upload to complete, so that's why the addDoc happens earlier than you want it to.
const uploadImage = async (file) => {
filePath.value = `${file.name}`;
const storageReference = storageRef(projectStorage,
filePath.value);
const uploadTask = uploadBytesResumable(storageReference,
file);
await uploadTask; // π Wait for the upload to finish
const downloadURL = getDownloadURL(uploadTask.snapshot.ref)
return downloadURL;
}
Now you can call it with:
newImage()
.then((downloadURL) => {
addDoc(colRef, {
content: content.value
});
})
Or, by using await again, with:
const downloadURL = await newImage();
addDoc(colRef, {
content: content.value
});
Related
I have sw.js which stores data in cache storage.
And there is a dataGrid that displays a list of users.
I want to add users and immediately see the changes, without sw.js everything works fine.
When I use the get api, I always get the cached response until I clear the cache and reload the page.
The cache is not updating.
How should i change this code to make it work correctly?
requests:
export const fetchUsers = createAsyncThunk(
"users/fetchUsers", async () => {
const response = await axiosInstance.get("api/users");
return response.data;
});
export const addNewUser = createAsyncThunk(
'users/addNewUser', async (newUser) => {
const response = await axiosInstance.post("api/users", newUser)
return response.data
})
sw.js
const staticCacheName = 'static-cache-v0';
const dynamicCacheName = 'dynamic-cache-v0';
const staticAssets = [
'./',
'./index.html',
'./images/icons/icon-128x128.png',
'./images/icons/icon-192x192.png',
'./offline.html',
'./css/main.css',
'./js/app.js',
'./js/main.js',
'./images/no-image.jpg'
];
self.addEventListener('install', async event => {
const cache = await caches.open(staticCacheName);
await cache.addAll(staticAssets);
console.log('Service worker has been installed');
});
self.addEventListener('activate', async event => {
const cachesKeys = await caches.keys();
const checkKeys = cachesKeys.map(async key => {
if (![staticCacheName, dynamicCacheName].includes(key)) {
await caches.delete(key);
}
});
await Promise.all(checkKeys);
console.log('Service worker has been activated');
});
self.addEventListener('fetch', event => {
console.log(`Trying to fetch ${event.request.url}`);
event.respondWith(checkCache(event.request));
});
async function checkCache(req) {
const cachedResponse = await caches.match(req);
return cachedResponse || checkOnline(req);
}
async function checkOnline(req) {
const cache = await caches.open(dynamicCacheName);
try {
const res = await fetch(req);
await cache.put(req, res.clone());
return res;
} catch (error) {
const cachedRes = await cache.match(req);
if (cachedRes) {
return cachedRes;
} else if (req.url.indexOf('.html') !== -1) {
return caches.match('./offline.html');
} else {
return caches.match('./images/no-image.jpg');
}
}
}
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
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);
}
}
I have an update function for an event. It is possible that the user has added a teaser video or not. If they have I want to upload it and save that object with the download url.
I use a different function to upload the video and only call it if the user attached a video.
But when I try to update, it tells me that I'm trying to write an invalid object because the data in teaser is a function, where I want it to be a string (either the download url, or just an empty string.
What am I doing wrong?
This is how I call the function:
updateEvent(values, videoAsFile, () => {
setIsEventEdited(false);
setCurrentEvent(values);
})
Then these are the functions:
const uploadTeaserVideo = (event, video) => async () => {
const ref = storage.ref(`/videos/events/${event.id}/`);
const upload = await ref.put(video);
if (!upload) return "";
const downloadUrl = await ref.getDownloadURL();
if (!downloadUrl) return "";
return downloadUrl;
};
export const updateEvent = (values, teaserVideo, cb) => async () => {
if (teaserVideo) {
const teaser = await uploadTeaserVideo(values, teaserVideo);
db.collection("events")
.doc(values.id)
.set({ ...values, teaser })
.then(() => {
cb();
});
} else {
db.collection("events")
.doc(values.id)
.set(values)
.then(() => {
cb();
});
}
};
I've checked, and teaserVideo is a valid video file, or null if a video wasn't chosen.
uploadTeaserVideo is defined as a function that returns a function:
// vvβββββββββββββββ Start of the uploadTeaserVideo function body
const uploadTeaserVideo = (event, video) => async () => {
// ^^βββ Start of the body of the function it returns
const ref = storage.ref(`/videos/events/${event.id}/`);
const upload = await ref.put(video);
if (!upload) return "";
const downloadUrl = await ref.getDownloadURL();
if (!downloadUrl) return "";
return downloadUrl;
};
I suspect you meant it to be an async function that returns (a promise of) downloadUrl:
const uploadTeaserVideo = async (event, video) => {
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();
});
};