Firebase image upload in async await manner - javascript

async UPLOAD_IMAGES() {
try {
let self = this;
var storageRef = firebase.storage().ref();
var files = document.getElementById("photoupload").files;
var file = files[0];
// Create the file metadata
var metadata = {
contentType: "image/jpeg"
};
// Upload file and metadata to the object 'images/mountains.jpg'
var uploadTask = storageRef
.child(`${this.name}/` + file.name)
.put(file, metadata);
// Listen for state changes, errors, and completion of the upload.
uploadTask.on(
firebase.storage.TaskEvent.STATE_CHANGED, // or 'state_changed'
function(snapshot) {
// Get task progress, including the number of bytes uploaded and the total number of bytes to be uploaded
var progress =
(snapshot.bytesTransferred / snapshot.totalBytes) * 100;
console.log("Upload is " + progress + "% done");
switch (snapshot.state) {
case firebase.storage.TaskState.PAUSED: // or 'paused'
console.log("Upload is paused");
break;
case firebase.storage.TaskState.RUNNING: // or 'running'
console.log("Upload is running");
break;
}
},
function(error) {
// A full list of error codes is available at
// https://firebase.google.com/docs/storage/web/handle-errors
switch (error.code) {
case "storage/unauthorized":
// User doesn't have permission to access the object
break;
case "storage/canceled":
// User canceled the upload
break;
case "storage/unknown":
// Unknown error occurred, inspect error.serverResponse
break;
}
},
function() {
// Upload completed successfully, now we can get the download URL
uploadTask.snapshot.ref
.getDownloadURL()
.then(function(downloadURL) {
console.log("File available at", downloadURL);
self = downloadURL;
});
}
);
} catch (error) {
console.log("ERR ===", error);
alert("Image uploading failed!");
}
}
this is my full function for the image upload. Only issue I have is it has callback functions inside this function.
function() {
// Upload completed successfully, now we can get the download URL
uploadTask.snapshot.ref
.getDownloadURL()
.then(function(downloadURL) {
console.log("File available at", downloadURL);
self = downloadURL;
});
}
Only for this part can I get async await function?

You can very well call then() on an UploadTask, as explained here: https://firebase.google.com/docs/reference/js/firebase.storage.UploadTask#then
This object behaves like a Promise, and resolves with its snapshot
data when the upload completes.
So you could do something like:
async UPLOAD_IMAGES() {
try {
let self = this;
const storageRef = firebase.storage().ref();
const files = document.getElementById("photoupload").files;
const file = files[0];
// Create the file metadata
const metadata = {
contentType: "image/jpeg"
};
const fileRef = storageRef
.child(`${this.name}/` + file.name);
const uploadTaskSnapshot = await fileRef.put(file, metadata);
const downloadURL = await uploadTaskSnapshot.ref.getDownloadURL();
self = downloadURL;
} catch (error) {
console.log("ERR ===", error);
alert("Image uploading failed!");
}
}

In case anyone needs to both observe upload progress while using await/async instead of callbacks, this code works:
try {
await saveToFirebase();
alert("Happy days!");
} catch (error) {
alert("Sad days!");
}
async function saveToFirebase() {
// Create promise.
let promise = $.Deferred();
// Set storage reference.
let storageRef = firebase.storage().ref();
// Se file path.
let filepath = "your-path.txt";
// Set content type.
let metadata = {
contentType: "text/plain",
cacheControl: "no-cache, max-age=0"
};
// Start upload.
let uploadTask = storageRef.child(filepath).putString(data, "raw", metadata);
// Monitor state of upload operation.
uploadTask.on(firebase.storage.TaskEvent.STATE_CHANGED, function(snapshot) {
// Get task progress, including number of bytes uploaded and total number of bytes to be uploaded.
let progress = (snapshot.bytesTransferred / snapshot.totalBytes);
// Display progress to user.
// Error during upload?
}, function(error) {
promise.reject(error);
// Nope, upload succeeded.
}, function() {
promise.resolve();
});
// Return promise.
return promise;
}

Related

i wanna save my uploaded images in a folder firebase storage

i wanna save my uploaded images in a folder called "course-cover" in firebase storage
this is my code that saves uploaded images in the route of storage directly but instead of thet i wanna save them in the "course-cover" folder
async function storeCoverCourse(coverCourse) {
return new Promise((resolve, reject) => {
const storage = getStorage();
const filename = `${coverCourse.name}-${uuidv4()}`;
const storageRef = ref(storage, filename);
const uploadTask = uploadBytesResumable(storageRef, coverCourse);
uploadTask.on(
"state_changed",
(snapshot) => {
// Observe state change events such as progress, pause, and resume
// Get task progress, including the number of bytes uploaded and the total number of bytes to be uploaded
const progress =
(snapshot.bytesTransferred / snapshot.totalBytes) * 100;
console.log("Upload is " + progress + "% done");
switch (snapshot.state) {
case "paused":
console.log("Upload is paused");
break;
case "running":
console.log("Upload is running");
break;
}
},
(error) => {
// Handle unsuccessful uploads
reject(error);
},
() => {
// Handle successful uploads on complete
// For instance, get the download URL: https://firebasestorage.googleapis.com/...
getDownloadURL(uploadTask.snapshot.ref).then((downloadURL) => {
resolve(downloadURL);
});
}
);
});
}
const imgUrls = await Promise.all(
[...cover].map((coverCourse) => storeCoverCourse(coverCourse))
).catch((error) => {
toast.error("image not uploaded");
return;
});
const formDataCopy = {
...formData,
imgUrls,
timestamp: serverTimestamp(),
};
delete formDataCopy.cover;
await addDoc(collection(db, "courses"), formDataCopy);
The storageRef is a StorageReference and you essentially specify path of your file with it. If your path contains /, that'll be like a directory. Try:
const filename = `course-cover/${coverCourse.name}-${uuidv4()}`;
const storageRef = ref(storage, filename);
It's not actually a folder under the hood but just a namespace. For more details, see How to create a folder in Firebase Storage?
In addition to the solution that #Dharmaraj covered in their answer, you should also get rid of the problematic use of the Promise constructor with Promise returning functions. It can be eliminated because the uploadTask object is a thenable/promise-like object.
async function storeCoverCourse(coverCourse) {
const storage = getStorage();
const filename = `course-cover/${coverCourse.name}-${uuidv4()}`;
const storageRef = ref(storage, filename);
const uploadTask = uploadBytesResumable(storageRef, coverCourse);
uploadTask.on(
"state_changed",
(snapshot) => {
// Observe state change events such as progress, pause, and resume
// Get task progress, including the number of bytes uploaded and the total number of bytes to be uploaded
const progress =
(snapshot.bytesTransferred / snapshot.totalBytes) * 100;
console.log("Upload is " + progress + "% done");
switch (snapshot.state) {
case "paused":
console.log("Upload is paused");
break;
case "running":
console.log("Upload is running");
break;
}
}
);
return uploadTask
.then(snapshot => snapshot.ref.getDownloadURL());
}

I manage to upload the image but on the firebase storage but it says undefined

I cant preview the image inside the store, it says undefined I manage to upload it on images/ folder but the name of the file is undefined instead of the actual name of the image
const uploadFile = () => {
console.log(selectedImages);
const storageRef = ref(storage, 'images/' + selectedImages.name);
const uploadTask = uploadBytesResumable(storageRef, selectedImages, metadata);
console.log(selectedImages);
uploadTask.on('state_changed',
(snapshot) => {
const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
console.log(progress);
console.log('Upload is ' + progress + '% done');
switch (snapshot.state) {
case 'paused':
console.log('Upload is paused');
break;
case 'running':
console.log('Upload is running');
break;
default:
break;
}
},
(error) => {
console.log(error);
},
() => {
// Upload completed successfully, now we can get the download URL
getDownloadURL(uploadTask.snapshot.ref).then((downloadURL) => {
console.log('File available at', downloadURL);
});
}
);
};
import {uploadString} from "#firebase/storage";//use this in rather than uploadbytes
uploadString(imageRef,imageToPost,'data_url')//use format of your file type it works for me

async/await in Firebase Storage (wait to get URL after uploading a file)

I'm useing Firestore in my Vue project and I'm working on a feature that allows users to upload images, however I'm facing the following issue:
I have a 'newMarker' placeholder object that the user fills in with information and once the user confirms the input, the object is saved to the database. When the user confirms the input I want to:
Save the image in Firebase Storage
Get the generated URL for that file
Add the URL to the newMarker object
and then push the newMarker object with the included imgURL to the database
However I can't figure out how to make the code wait for the callback function that runs as soon as the upload status is completed (in the uploadIMG function, 'uploadTask.on(firebase.storage.TaskEvent.STATE_CHANGED', last callback function) and returns a promise with the url. No matter what I do, this callback function is executed last.
When the user confirms the input, saveNewMarker() is executed and the following code will run:
My code:
async saveNewMarker() {
await this.uploadImg();
console.log('Image upload finished! Pushing new marker to db')
await db.collection(this.user.email).add({
position: this.newMarker.position,
type: this.newMarker.type,
location: this.newMarker.location,
imgURL: this.newMarker.imgURL
})
.then((marker) => {
console.log('marker added to database')
this.newMarker.id = marker.id
})
},
async uploadImg(){
console.log('Uploading image ...')
const storageRef = firebase.storage().ref();
const uploadTask = storageRef.child('user-uploads/images/' + this.file.name).put(this.name)
uploadTask.on(firebase.storage.TaskEvent.STATE_CHANGED,
(snapshot) => {
const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
console.log('Upload is ' + progress + '% done');
},
(error) => {
console.log(error)
},
async () => {
const imgURL = await uploadTask.snapshot.ref.getDownloadURL()
console.log('uploaded image: ' + imgURL)
this.newMarker.imgURL = imgURL
}
);
},
Output:
Uploading image ...
Upload is NaN% done
Image upload finished! Pushing new marker to db
marker added to database
uploaded image: https://firebasestorage.googleapis.com/v0/b/....
Expected output:
Uploading image ...
Upload is NaN% done
uploaded image: https://firebasestorage.googleapis.com/v0/b/....
Image upload finished! Pushing new marker to db
marker added to database
That's not how async/awaits are intended to be used, I would recommend using Promises instead, like this:
function saveNewMarker() {
// Call uploadImg as a Promise and wait for the result
this.uploadImg()
.then((imgURL) => {
console.log('Image upload finished! Pushing new marker to db');
db.collection(this.user.email).add({
position: this.newMarker.position,
type: this.newMarker.type,
location: this.newMarker.location,
imgURL: this.newMarker.imgURL
})
.then((marker) => {
console.log('marker added to database');
this.newMarker.id = marker.id;
})
}).catch((error) => {
//Do something
});
};
function uploadImg() {
// Return a promise that will either resolve or emit an error
return new Promise((resolve, reject) => {
console.log('Uploading image ...');
const storageRef = firebase.storage().ref();
const uploadTask = storageRef.child('user-uploads/images/' + this.file.name).put(this.name);
uploadTask.on(firebase.storage.TaskEvent.STATE_CHANGED,
(snapshot) => {
const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
console.log('Upload is ' + progress + '% done');
},
(error) => {
console.log(error);
// An error occurred so inform the caller
reject(error);
},
async () => {
const imgURL = await uploadTask.snapshot.ref.getDownloadURL();
console.log('uploaded image: ' + imgURL);
this.newMarker.imgURL = imgURL;
// We 'awaited' the imgURL, now resolve this Promise
resolve(imgURL);
}
);
});
};
And remember your semicolons to avoid unintended behavior or errors.
You will have to wrap code within uploadImg in promise and resolve it once image upload is done. Refactoring uploadImg to something like below should work:
async uploadImg() {
return new Promise((resolve, reject) => {
console.log("Uploading image ...");
const storageRef = firebase.storage().ref();
const uploadTask = storageRef.child("user-uploads/images/" + this.file.name).put(this.name);
uploadTask.on(
firebase.storage.TaskEvent.STATE_CHANGED,
(snapshot) => {
const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
console.log("Upload is " + progress + "% done");
},
(error) => {
console.log(error);
reject(error);
},
async () => {
const imgURL = await uploadTask.snapshot.ref.getDownloadURL();
console.log("uploaded image: " + imgURL);
this.newMarker.imgURL = imgURL;
resolve();
}
);
});
},

Firebase cloud function storage trigger first thumbnail urls are fine then the next ones are all the same thumbnails urls as the first

I am trying to upload an image to firebase and then produce 2 thumbnails. I am able to do this with no problems. My current road block is when I write the urls to the realtime database, I am always getting the same url as the initial upload.
For example:
1st upload I get my uploaded image with the two proper thumbnails for the image
2nd upload I get my uploaded image with the two previous thumbnails (first image)
3rd upload I get my uploaded image with the first images thumbnails...
...this continues to reproduce the urls for the first upload
In my storage the correct thumbnails are being generated, but the urls are always for the first upload?
I don't know if this is a problem with the getSignedUrl() or not, really not sure whats going on here.
Here is my cloud function:
export const generateThumbs = functions.storage
.object()
.onFinalize(async object => {
const bucket = gcs.bucket(object.bucket); // The Storage object.
// console.log(object);
console.log(object.name);
const filePath = object.name; // File path in the bucket.
const fileName = filePath.split('/').pop();
const bucketDir = dirname(filePath);
const workingDir = join(tmpdir(), 'thumbs');
const tmpFilePath = join(workingDir, 'source.png');
if (fileName.includes('thumb#') || !object.contentType.includes('image')) {
console.log('exiting function');
return false;
}
// 1. ensure thumbnail dir exists
await fs.ensureDir(workingDir);
// 2. Download Sounrce fileName
await bucket.file(filePath).download({
destination: tmpFilePath
});
//3. resize the images and define an array of upload promises
const sizes = [64, 256];
const uploadPromises = sizes.map(async size => {
const thumbName = `thumb#${size}_${fileName}`;
const thumbPath = join(workingDir, thumbName);
//Resize source image
await sharp(tmpFilePath)
.resize(size, size)
.toFile(thumbPath);
//upload to gcs
return bucket.upload(thumbPath, {
destination: join(bucketDir, thumbName),
metadata: {
contentType: 'image/jpeg'
}
}).then((data) => {
const file = data[0]
// console.log(data)
file.getSignedUrl({
action: 'read',
expires: '03-17-2100'
}).then((response) => {
const url = response[0];
if (size === 64) {
// console.log('generated 64');
return admin.database().ref('profileThumbs').child(fileName).set({ thumb: url });
} else {
// console.log('generated 128');
return admin.database().ref('categories').child(fileName).child('thumb').set(url);
}
})
.catch(function (error) {
console.error(err);
return;
});
})
});
//4. Run the upload operations
await Promise.all(uploadPromises);
//5. Cleanup remove the tmp/thumbs from the filesystem
return fs.remove(workingDir);
})
Cleaned up my code and solved my problem, here is how I generated the urls and passed them to the proper URLs by accessing the users UID and postId in the file path:
export const generateThumbs = functions.storage
.object()
.onFinalize(async object => {
const fileBucket = object.bucket; // The Storage bucket that contains the file.
const filePath = object.name; // File path in the bucket.
const fileName = filePath.split('/').pop();
const userUid = filePath.split('/')[2];
const sizes = [64, 256];
const bucketDir = dirname(filePath);
console.log(userUid);
if (fileName.includes('thumb#') || !object.contentType.includes('image')) {
console.log('exiting function');
return false;
}
const bucket = gcs.bucket(fileBucket);
const tempFilePath = path.join(tmpdir(), fileName);
return bucket.file(filePath).download({
destination: tempFilePath
}).then(() => {
sizes.map(size => {
const newFileName = `thumb#${size}_${fileName}.png`
const newFileTemp = path.join(tmpdir(), newFileName);
const newFilePath = `thumbs/${newFileName}`
return sharp(tempFilePath)
.resize(size, size)
.toFile(newFileTemp, () => {
return bucket.upload(newFileTemp, {
destination: join(bucketDir, newFilePath),
metadata: {
contentType: 'image/jpeg'
}
}).then((data) => {
const file = data[0]
console.log(data)
file.getSignedUrl({
action: 'read',
expires: '03-17-2100'
}, function(err, url) {
console.log(url);
if (err) {
console.error(err);
return;
}
if (size === 64) {
return admin.database().ref('profileThumbs').child(userUid).child(fileName).set({ thumb: url });
} else {
return admin.database().ref('categories').child(fileName).child('thumb').set(url);
}
})
})
})
})
}).catch(error =>{
console.log(error);
});
})

Firebase get Download URL after successful image upload to firebase storage

I am trying to upload a single image to Firebase Storage, then grab its download url and assign this to a variable.
I can upload my image to firebase successfully, however I cannot retrieve the download url. here is what I have tried already.
upload() {
let storageRef = firebase.storage().ref();
let success = false;
for (let selectedFile of [(<HTMLInputElement>document.getElementById('file')).files[0]]) {
let router = this.router;
let af = this.af;
let folder = this.folder;
let path = `/${this.folder}/${selectedFile.name}`;
var iRef = storageRef.child(path);
iRef.put(selectedFile).then((snapshot) => {
console.log('Uploaded a blob or file! Now storing the reference at', `/${this.folder}/images/`);
af.list(`/${folder}/images/`).push({ path: path, filename: selectedFile.name })
});
}
// This part does not work
iRef.getDownloadURL().then((url) => {this.image = url});
console.log('IREF IS ' + iRef)
console.log('IMAGEURL IS ' + this.image)
}
The Console logs are these:
IREF IS gs://my-app-159520.appspot.com/images/Screen Shot 2017-08-14 at 12.19.01.png
view-order.component.ts:134 IMAGEURL IS undefined
Uploaded a blob or file! Now storing the reference at /images/images/
I have been trying to use the iRef reference to grab the download url but I keep getting errors. I am trying to grab the url so I can assign it to the this.image variable and then store it in my database using another function.
The API has changed. Use the following to get downloadURL
snapshot.ref.getDownloadURL().then(function(downloadURL) {
console.log("File available at", downloadURL);
});
I think I have figured this out and it seems to be working, I realised I had to grab the downloadURL from the snapshot and assign that to this.image like so:
upload() {
let storageRef = firebase.storage().ref();
let success = false;
for (let selectedFile of [(<HTMLInputElement>document.getElementById('file')).files[0]]) {
let router = this.router;
let af = this.af;
let folder = this.folder;
let path = `/${this.folder}/${selectedFile.name}`;
var iRef = storageRef.child(path);
iRef.put(selectedFile).then((snapshot) => {
// added this part which as grabbed the download url from the pushed snapshot
this.image = snapshot.downloadURL;
console.log('Uploaded a blob or file! Now storing the reference at', `/${this.folder}/images/`);
af.list(`/${folder}/images/`).push({ path: path, filename: selectedFile.name })
console.log('DOWNLOAD URL IS ' + this.image)
});
}
}
I then ran my other function to add the URL to the database and it has gone in ok where expected!
So I have uploaded the image to the database, then using the snapshot from the put function, I then assigned my variable image:any to to the snapshot downloadURL like so:
this.image = snapshot.downloadURL;
I hope this can help someone else!
.put() function is returning a task, which can be used to track the uploading state.
For example you can listen for progress, error or completion like so:
onUploadImage () {
const self = this
const file = self.selectedFile
if (!file) {
return
}
self.isUploading = true
const storageRef = firebase.storage().ref('/images/' + file.name)
const task = storageRef.put(file)
task.on('state_changed',
function progress (snapshot) {
self.status = 'UPLOADING...'
self.percentage = (snapshot.bytesTransferred / snapshot.totalBytes) * 100
},
function error () {
self.status = 'FAILED TRY AGAIN!'
self.isUploading = false
},
function complete (event) {
self.status = 'UPLOAD COMPLETED'
self.isUploading = false
storageRef.getDownloadURL().then((url) => { console.log(url) })
}
)
}
In 2019, I gained access to the url of a newly-saved file in firebase with the following function:
const uploadImage = async(uri) => {
const response = await fetch(uri);
const blob = await response.blob();
// child arg specifies the name of the image in firebase
const ref = firebase.storage().ref().child(guid());
ref.put(blob).then(snapshot => {
snapshot.ref.getDownloadURL().then(url => {
console.log(' * new url', url)
})
})
}
As of web version v9 (modular) you can achieve this in this way:
import { getStorage, ref, uploadBytes, getDownloadURL } from "firebase/storage";
uploadImage = () => {
const storage = getStorage()
const reference = ref(storage, 'file_name.jpg')
const file = e.target.files[0]
uploadBytes(reference, file)
.then(snapshot => {
return getDownloadURL(snapshot.ref)
})
.then(downloadURL => {
console.log('Download URL', downloadURL)
})
}

Categories