How to await the result from an async function? [duplicate] - javascript

This question already has answers here:
How to "await" for a callback to return?
(5 answers)
Closed 1 year ago.
I'm currently working with Firebase to upload an image to the storage.
I created an uploadImage function which takes an image and saves it to Firebase Storage then returns the url of that saved image. When the user clicks the Submit button, I use that uploadImage function to save that image and get back a url. The image is properly being saved, but the url that is being logged is undefined. I'm pretty sure it's an issue with the aysnc-await implementation I have. Any thoughts on this? Thank you!
UploadForm.js:
import { uploadImage } from "../firebase/functions";
const UploadForm = () => {
const [image1, setImage1] = useState(null);
const saveForm = async (file) => {
const url1 = await uploadImage(image1);
console.log("URL", url1); //the url here is UNDEFINED
};
return (
<ImageUploadForm setImageProp={setImage1} />
<button
onClick={(e) => {
e.preventDefault();
saveForm(image1);
}}
></button>
);
uploadImage function:
const uploadImage = async (file) => {
const storageRef = storageService.ref().child("posts/" + file.name);
storageRef.put(file).on(
"state_changed",
(snapshot) => {},
(error) => {
console.log(error);
},
async () => {
return await storageRef.getDownloadURL();
}
);
};

Your uploadImage function doesn't return anything, just return the promise
return storageRef.put(file).on(
or, (since I don't know how that function works / what it returns), possibly
const uploadImage = (file) => {
return new Promise((resolve, reject) => {
const storageRef = storageService.ref().child("posts/" + file.name);
storageRef.put(file).on(
"state_changed",
(snapshot) => {},
(error) => {
console.log(error);
reject(error);
},
() => {
const res = storageRef.getDownloadURL();
resolve(res);
}
);
};
}

Related

How can I upload multiple images in firebase at react in different method?

I want to console.log() in the console.log(list) the uploadBytes of my image, so that I could implement it and pass it at my backend without separating it.
The code is something like this
const handleSubmitForm = e => {
e.preventDefault()
alert("Your Images Is Uploaded")
const imagesRef = ref(storage,`GALLERY/${image.name}${v4()}`)
const imagesRef2 = ref(storage,`GALLERY/${image2.name}${v4()}`)
const imagesRef3 = ref(storage,`GALLERY/${image3.name}${v4()}`)
const imagesRef4 = ref(storage,`GALLERY/${image4.name}${v4()}`)
const imagesRef5 = ref(storage,`GALLERY/${image5.name}${v4()}`)
const id = v4()
const Uploads = async() => {
const list = []
uploadBytes(imagesRef,image)
.then((snapshot) => {
getDownloadURL(snapshot.ref).then((url) => {
console.log('Image uploaded' + `${url}`)
const item = url
list.push(item)
})
})
uploadBytes(imagesRef2,image2)
.then((snapshot) => {
getDownloadURL(snapshot.ref).then((url) => {
console.log('Image uploaded' + `${url}`)
const item = url
list.push(item)
})
})
uploadBytes(imagesRef3,image3)
.then((snapshot) => {
getDownloadURL(snapshot.ref).then((url) => {
console.log('Image uploaded' + `${url}`)
const item = url
list.push(item)
})
})
uploadBytes(imagesRef4,image4)
.then((snapshot) => {
getDownloadURL(snapshot.ref).then((url) => {
console.log('Image uploaded' + `${url}`)
const item = url
list.push(item)
})
})
uploadBytes(imagesRef5,image5)
.then((snapshot) => {
getDownloadURL(snapshot.ref).then((url) => {
console.log('Image uploaded' + `${url}`)
const item = url
list.push(item)
})
})
console.log(list)
}
Uploads()
}
My only problem here is in the const list = [], wherein I want to append every uploadBytes in my list, so that I will not have to repeatedly called the backend, cause Imma put a backend there in every uploadBytes, but I want to make it more easy as just to push it in the list.
I tried to do it in async but it doesn't work out. It does just give me an empty array on it cause I don't know? I'm just thinking I need to make the uploadBytes as async but still doesn't work.
If I understand your question correctly you want to log the list of download URLs after all the images have been uploaded.
This requires that you handle two asynchronous operations for each upload:
the upload of the data itself,
the call to get the download URL.
Since you need to do this for every image, you'll need a Promise.all, and since it's two calls, you'll need a chained then operation.
Combining this leads to:
// Helper function to upload an image and get back its download URL
const UploadSingleImage = (ref, image) => {
return uploadBytes(ref, image).then((snapshot) => {
return getDownloadURL(ref);
})
}
const Uploads = async () => {
const list = await Promise.all([
UploadSingleImage(imagesRef, image),
UploadSingleImage(imagesRef2, image2),
UploadSingleImage(imagesRef3, image3),
UploadSingleImage(imagesRef4, image4),
UploadSingleImage(imagesRef5, image5)
]);
console.log(list)
}

Firebase image uploaded Urls are taking time to set on my state url state

I am learning to react js with typescript. The function I wrote uploads the images into firebase when the action submits button is clicked and it returns an array of URLs. Everything works fine but it takes a lot of time. In the if statement, I do not want to go to the if statement until the uploaded URls are set into the state because I need to add the URLs into my product object I have used promises and async-await but it is not working while the state is true.
If you want you can see the live code: https://codesandbox.io/s/eloquent-pine-tdv3wf?file=/src/AddProduct.tsx:1864-2218
This is the function that will set the state of the URLs into an array. Also, I have created promises after the success it will set true into the setSuccess state.
//States
const [success, setSuccess] = useState<boolean>(false)
const [images, setImages] = useState<any>([])
const [URLs, setURLs] = useState<any>([])
const uploadFiles = async (files: any) => {
const promises: any = []
files.map((file: any) => {
const sotrageRef = ref(storage, `files/${file.name}`);
const uploadTask = uploadBytesResumable(sotrageRef, file);
promises.push(uploadTask)
uploadTask.on(
"state_changed",
(snapshot: any) => {
const prog = Math.round(
(snapshot.bytesTransferred / snapshot.totalBytes) * 100
);
setProgress(prog);
},
(error: any) => console.log(error),
async () => {
await getDownloadURL(uploadTask.snapshot.ref).then((downloadURLs: any) => {
setURLs((prevState: any) => [...prevState, downloadURLs])
console.log("File available at", downloadURLs);
});
}
);
})
try {
await Promise.all(promises);
setSuccess(true);
return true;
} catch (e) {
console.error(e);
return false;
}
};
After clicking on submit action button this handleProductSubmit function will call and then it will call the uploadfiles function to set the uploaded files URLs into an array. If the success state is true then it will go to the next step for adding the URLs into my product data. **The problem is it goes to the next step because uploadWasSuccessful is true but my URLs array is still empty. After this, if statement my URLs state set the urls **
const handleProductSubmit = async (e: any) => {
e.preventDefault()
const uploadWasSuccessful: any = await uploadFiles(images)
console.log('uploadWasSuccessful', uploadWasSuccessful);
if (uploadWasSuccessful) {
const newProductValue = { ...productValue, URLs }
console.log(newProductValue, 'productValue');
}
}
The issue is here: setURLs state takes the URLs after rendering all my functions. Basically, it takes time to upload the files, and then it sets the URLs of the file.
async () => {
await getDownloadURL(uploadTask.snapshot.ref).then((downloadURLs: any) => {
setURLs((prevState: any) => [...prevState, downloadURLs])
console.log("File available at", downloadURLs);
});

if statement is not working while the condition is true

I am learning to react js with typescript. The function I wrote uploads the images into firebase when the action submits button is clicked and it returns an array of URLs. Everything works fine but it takes a lot of time. In the if statement, I do not want to go to the if statement until the upload is complete since I need to put the URLs array into the product object. I have used promises and async-await but it is not working while the state is true.
If you want you can see the live code: https://codesandbox.io/s/eloquent-pine-tdv3wf?file=/src/AddProduct.tsx:1864-2218
This is the function that will set the state of the URLs into an array. Also, I have created promises after the success it will set true into the setSuccess state.
//States
const [success, setSuccess] = useState<boolean>(false)
const [images, setImages] = useState<any>([])
const [URLs, setURLs] = useState<any>([])
const uploadFiles = async (files: any) => {
const promises: any = []
files.map((file: any) => {
const sotrageRef = ref(storage, `files/${file.name}`);
const uploadTask = uploadBytesResumable(sotrageRef, file);
promises.push(uploadTask)
uploadTask.on(
"state_changed",
(snapshot: any) => {
const prog = Math.round(
(snapshot.bytesTransferred / snapshot.totalBytes) * 100
);
setProgress(prog);
},
(error: any) => console.log(error),
async () => {
await getDownloadURL(uploadTask.snapshot.ref).then((downloadURLs: any) => {
setURLs((prevState: any) => [...prevState, downloadURLs])
console.log("File available at", downloadURLs);
});
}
);
})
try {
await Promise.all(promises);
setSuccess(true);
return true;
} catch (e) {
console.error(e);
return false;
}
};
After clicking on submit action button this handleProductSubmit function will call and then it will call the uploadfiles function to set the uploaded files URLs into an array. If the success state is true then it will go to the next step for adding the URLs into my product data. **The problem is it goes to the next step because uploadWasSuccessful is true but my URLs array is still empty. After this, if statement my URLs state set the urls **
const handleProductSubmit = async (e: any) => {
e.preventDefault()
const uploadWasSuccessful: any = await uploadFiles(images)
console.log('uploadWasSuccessful', uploadWasSuccessful);
if (uploadWasSuccessful) {
const newProductValue = { ...productValue, URLs }
console.log(newProductValue, 'productValue');
}
}
The issue is here: setURLs state takes the URLs after rendering all my functions. Basically, it takes time to upload the files, and then it sets the URLs of the file.
async () => {
await getDownloadURL(uploadTask.snapshot.ref).then((downloadURLs: any) => {
setURLs((prevState: any) => [...prevState, downloadURLs])
console.log("File available at", downloadURLs);
});
uploadFiles is asynchronous, so you need to wait until everything is finished there before continuing:
const handleProductSubmit = async (e: any) => {
e.preventDefault()
const uploadWasSuccessful = await uploadFiles(images)
if (uploadWasSuccessful) {
const newProductValue = { ...productValue, URLs }
console.log(newProductValue, 'newProductValue');
}
}
Then change uploadFiles to return the result instead of setting state (unless you need that state in other places, in which case both set the state and return the value):
const uploadFiles = async (files: any) => {
// ... snip ...
try {
await Promise.all(promises);
setSuccess(true);
return true;
} catch (e) {
console.error(e);
return false;
}
}

Uploading base64 to the firebase storage and getting back the download url

The first function called is addPostHandler, which is calling function storeImage. Inside storeImage function, postData.postImage is Array of string (Images converted into base64). What I am trying to do here is map all the images in postData.postImage and then upload it into the firestore, then get the download Url and push it into imageUrl. After I finish uploading all the images and getting the URL, at the end I want that console.log("Printing....") to run.
Error is storeImage function is returning empty string instead of downloadUrl.
const index = () => {
const storeImage = async (postData: PostType) => {
const imageUrl: string[] = [];
const storage = getStorage();
postData.postImage.map((image, i) => {
const storageRef = ref(
storage,
`ImagesOfPosts/${postData.userId}/${postData.postId}/image ${i}`
);
uploadString(storageRef, image, "data_url", {
contentType: "image/jpg",
}).then(() => {
getDownloadURL(storageRef)
.then((result) => {
imageUrl.push(result);
})
.catch((error) => {
console.error(error);
});
});
});
console.log(imageUrl);
return imageUrl;
};
const addPostHandler = async (enteredPostData: PostType) => {
const imageUrl = await storeImage(enteredPostData);
console.log("Printing.......");
}
Your top-level code is not waiting until the upload and getting the download URL are finished, so you're gonna see it return an empty array.
Since you're uploading multiple images, you'll need to use Promise.all() to only resolve storeImage when all image are done.
In total that'll be something like:
const storeImage = async (postData: PostType) => {
const storage = getStorage();
const imageUrl = Promise.all(postData.postImage.map((image, i) => {
const storageRef = ref(
storage,
`ImagesOfPosts/${postData.userId}/${postData.postId}/image ${i}`
);
return uploadString(storageRef, image, "data_url", {
contentType: "image/jpg",
}).then(() => {
return getDownloadURL(storageRef);
});
});
return imageUrl;
};

How to write an asynchronous function

I'm doing my first ever react website and I need help to write an asynchronous JavaScript function.
Here I'm uploading the user input files to a firebase storage and then making a post request to the API to store the data on the database. However, since the firebase upload takes some time to upload the data to its storage, the API request happens before the upload finishes, therefore the data does not get uploaded to the db. Now I know I should use promises of async await keywords here, but I can't figure out how to. I'd appreciate if someone could help. Thanks in advance!
Here's the relevant code snippet:
const save = (items) => {
items.forEach((item) => {
const fileName = new Date().getTime() + item.label + item.file.name;
const uploadTask = storage.ref(`/items/${fileName}`).put(item.file);
uploadTask.on(
"state_changed",
(snapshot) => {
const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
console.log("Upload is" + progress + "% done.");
},
(err) => {
console.log(err)
},
() => {
storage.ref("items").child(fileName).getDownloadURL().then((url) => {
setSong((prev) => {
return { ...prev, [item.label]: url };
});
});
}
);
})
console.log(song)
axios.post("songs/create", song);
}
PS: Here, items is the array of input files from the user, each file is with a label and it is how the attributes are named on the json document.
setSong is a useState function. Here The JSON document already contains the other user inputs(which are not files), and the setSong method is used to append the firebase URLs of the files to it.
You have to wait for all files to get uploaded then you can call your API, in order to do that you should use Promise.all to wait to resolve all files :
const save = items => {
Promise.all(
items.map(item => {
return new Promise(resolve => {
const fileName = new Date().getTime() + item.label + item.file.name
const uploadTask = storage.ref(`/items/${fileName}`).put(item.file)
uploadTask.on(
'state_changed',
snapshot => {
const progress =
(snapshot.bytesTransferred / snapshot.totalBytes) * 100
console.log('Upload is' + progress + '% done.')
},
err => {
console.log(err)
},
() => {
storage
.ref('items')
.child(fileName)
.getDownloadURL()
.then(url => {
setSong(prev => {
return { ...prev, [item.label]: url }
})
resolve({[item.label]: url})
})
}
)
})
})
).then((res) => {
const song = {}
res.forEach(item => {
return {
...song,
...item
}
})
axios.post('songs/create', song)
})
}
Explanation
Functions and Async
Async/Await can be implemented wherever a function starts. Functions can be written in following forms:
function name(){};
function name() => {};
To write an async function, you would do the following:
async function name(){};
All of these functions are called functions though, to make functions run without calling them, we need to turn them into IIFE's, or Immediately Invoked Function Execution. If you want to create a function and execute it immediately you would surround the function in ()'s and end it with an ();.
(function () {})();
If we simplify this:
(() => {})();
Implementing async would go like this:
(async () => {})();
Await
The await operator is used to wait for a Promise, puting await in front of an expression that uses promises makes it wait for the promise. If it is used in front of an expression that doesn't use promises, it is redundant and your code editor/IDE will say so.
(async () => {
const str = await 'some string';
console.log(str);
})();
await is redundant here since the expression 'some string' does not relate to a promise, but a string.
(async () => {
const myPromise = new Promise((resolve, reject) =>
resolve('some string')
);
const str = await myPromise.then(x => console.log(x));
})();
await is properly used here since the expression myPromise is related to a promise that outputs a string.
Implementation
I'm not 100% sure how the api works within promises, I recommend you figure it out yourself, but this is my educated guess:
const save = (items) => {
Promise.all(
items.map((item) => {
return new Promise(async (resolve) => {
const fileName = new Date().getTime() + item.label + item.file.name;
const uploadTask = await storage
.ref(`/items/${fileName}`)
.put(item.file);
await uploadTask.on(
"state_changed",
(snapshot) => {
const progress =
(snapshot.bytesTransferred / snapshot.totalBytes) * 100;
console.log("Upload is" + progress + "% done.");
},
(err) => {
console.log(err);
},
async () => {
await storage
.ref("items")
.child(fileName)
.getDownloadURL()
.then((url) => {
setSong((prev) => {
return { ...prev, [item.label]: url };
});
resolve({ [item.label]: url });
});
}
);
});
})
).then(async (res) => {
const song = {};
res.forEach((item) => {
return {
...song,
...item,
};
});
await axios.post("songs/create", song);
});
};

Categories