I am doing an Image Upload feature with Cloudinary. I'm providing an array which may contains base64coded or uploaded image which is a url :
[
"https://res.cloudinary.com/\[userName\]/image/upload/v167xxxx4/luxxxfsgasxxxxxx7t9.jpg", "https://res.cloudinary.com/doeejabc9/image/upload/v1675361225/rf6adyht6jfx10vuzjva.jpg",
"data:image/jpeg;base64,/9j/4AAUSkZJRgABAQEBLAEsAA.......", "data:image/jpeg;base64,/9j/4AAUSkZJRgABAQEBLAEsAA......."
]
I'm using this function to upload the "un-uploaded", which returns the all uploaded version:
export async function uploadImage(el: string[]) {
const partition = el.reduce(
(result: string[][], element: string) => {
element.includes("data:image/")
? result[0].push(element)
: result[1].push(element);
return result;
},
[[], []]
);
for (let i = 0; i < partition[0].length; i++) {
const data = new FormData();
data.append("file", partition[0][i]);
data.append("upload_preset", "my_preset_name");
const res = await fetch(
"https://api.cloudinary.com/v1_1/userName/image/upload",
{
method: "POST",
body: data,
}
);
const file = await res.json();
partition[1].push(file.secure_url);
console.log(partition[1]);
}
return partition[1];
}
Then I will use the return value to update the state and call the api to update database:
const uploaded = await uploadImage(el[1])
console.log(uploaded);
setFinalVersionDoc({
...chosenDocument,
[chosenDocument[el[0]]]: uploaded,
});
However, it always updates the useState before the console.log(uploaded). I thought async/await would make sure the value is updated before moving on.
The GitHub repo is attached for better picture. The fragment is under EditModal in the 'component/document' folder:
https://github.com/anthonychan1211/cms
Thanks a lot!
I am hoping to make the upload happen before updating the state.
The function is correct, but you are trying to await the promise inside the callback function of a forEach, but await inside forEach doesn't work.
This doesn't work:
async function handleEdit() {
const entries = Object.entries(chosenDocument);
entries.forEach(async (el) => { // <------ the problem
if (Array.isArray(el[1])) {
const uploaded = await uploadImage(el[1]);
el[1].splice(0, el[1].length, uploaded);
}
});
[...]
}
If you want to have the same behaviour (forEach runs sequentially), you can use a for const of loop instead.
This works (sequentially)
(execution order guaranteed)
async function handleEdit() {
const entries = Object.entries(chosenDocument);
for (const el of entries) {
// await the promises 1,2,...,n in sequence
if (Array.isArray(el[1])) {
const uploaded = await uploadImage(el[1]);
el[1].splice(0, el[1].length, uploaded);
}
}
}
This also works (in parallel)
(execution order not guaranteed)
async function handleEdit() {
const entries = Object.entries(chosenDocument);
await Promise.all(entries.map(async (el) => {
// map returns an array of promises, and await Promise.all() then executes them all at the same time
if (Array.isArray(el[1])) {
const uploaded = await uploadImage(el[1]);
el[1].splice(0, el[1].length, uploaded);
}
}));
[...]
}
If the order in which your files are uploaded doesn't matter, picking the parallel method will be faster/better.
Related
I'm using nextjs 13, just started. I'm using getServerSideProps() but the request is rethrown every time it comes to the page. I want to check my Store field first, if the store field is empty, then it should send a request. how can I do it.
// current
export const getServerSideProps = async () => {
const { data } = await axios.get(`https://www.freetogame.com/api/games`);
return { props: { data: data } };
};
// i want
export const getServerSideProps = async () => {
if(store.gamesState.length <0 ){
const { data } = await axios.get(`https://www.freetogame.com/api/games`);
dispatch(gamesActions.setGames(data))
}
};
I'm trying to get photos in map function from Nest.js server. This is slider component. I give an array of slides as input and show on within a map.
The thing is, I need to get real file from server (by res.sendFile..) while doing .map.
I searched and tried a few variants, this is the last, but still get errors.
what do I do wrong? Please, help
const Slider = async ({videos}) => {
async function getImageHandler(fileName) {
return getImageFromNestServer(fileName).then(async (response) => {
const fileType = await FileType.fromBuffer(response.data);
return `data:${fileType.mime};base64, ${ArrayBufferConverter.encode(response.data)}`;
})
}
let realImg = await Promise.all(
videos.map(async video => {
try {
video.fetchItem = await getImageHandler(video.content_preview_photo.filename)
return video;
} catch(err) {
throw err;
}
}
)
)
return (<MySlide>...</MySlide>)
}
I am writing a simple program with React + Redux.
Inside createAsyncThunk, I need to send multiple requests to send data chunks.
Now my codes are something like the bellow;
export const sendData = createAsyncThunk<
void,
{
id: string;
data: Uint8Array;
}
>("files/upload", async (props) => {
const { uploadId } = await api.init();
try {
let i = 0;
while (props.data.length > i * CHUNK_SIZE) {
const chunkedData ... // Here I create chunked data from props.data.
await api.uploadFileChunk({
uploadId: uploadId,
data: chunkedData,
});
i++;
}
await api.complete({
id: props.id,
uploadId: uploadId,
});
} catch (err) {
await api.abort({ uploadId: uploadId });
}
});
Currently, I show a loading bar while sending data but it does not tell the progress. I want to improve the program to show a progress bar because it takes a long time if the data is large to send.
How can I manage progress with createAsyncThunk?
You need to be dispatching some sort of action each time that a chunk is loaded. That way your store is aware of how much has been loaded and can select the progress. The createAsyncThunk function is designed to create three action creators for pending, fulfilled, and rejected.
There is a second argument after props which you have access to in your callback. That is the thunkAPI object which contains your store's dispatch method. You should be able to use this dispatch additional actions from inside your main payloadCreator callback.
Be careful with catch-ing errors here. I'm not sure but I think this will cause a fulfilled response so you would need to use rejectWithValue.
It should look something like this (obviously untested)
export const sendData = createAsyncThunk<
void,
{
id: string;
data: Uint8Array;
}
>(
"files/upload",
async (props, {dispatch, rejectWithValue}) => {
const { uploadId } = await api.init();
try {
let i = 0;
while (props.data.length > i * CHUNK_SIZE) {
const chunkedData = // Here I create chunked data from props.data.
await api.uploadFileChunk({
uploadId: uploadId,
data: chunkedData,
});
i++;
dispatch({
type: "files/uploadProgress",
percent: //calculate this
});
}
await api.complete({
id: props.id,
uploadId: uploadId,
});
} catch (err) {
await api.abort({ uploadId: uploadId });
rejectWithValue(err);
}
});
If I'm reading your loop correctly, I think you want i += CHUNK_SIZE rather than i++ since you upload a bunch at a time. I'm leaving it up to you to figure out the progress calculations.
I'm starting my first serious app with Vue.js and I have an issue gathering data from Firabase. The idea here is simply to get data linked to an user ID. My first though was to store that in a computed value, like so
export default {
...
computed: {
userInfo: function() {
const firestore = firebase.firestore();
const docPath = firestore.doc('/users/' + firebase.auth().currentUser.uid);
docPath.get().then((doc) => {
if (doc && doc.exists) {
return doc.data();
}
});
}
}
}
But, when I try to access this variable, it's undifined.
My guess is that the value is computed before the asynchronous call has ended. But I can't see how to get around it.
Indeed you have to take into account the asynchronous character of the get() method. One classical way is to query the database in the created hook, as follows:
export default {
data() {
return {
userInfo: null,
};
},
....
created() {
const firestore = firebase.firestore();
const docPath = firestore.doc('/users/' + firebase.auth().currentUser.uid);
docPath.get().then((doc) => {
if (doc && doc.exists) {
this.userInfo = doc.data();
}
});
}
}
I would like to call an asynchronous function outside the lambda handler with by the following code:
var client;
(async () => {
var result = await initSecrets("MyWebApi");
var secret = JSON.parse(result.Payload);
client= new MyWebApiClient(secret.API_KEY, secret.API_SECRET);
});
async function initSecrets(secretName) {
var input = {
"secretName" : secretName
};
var result = await lambda.invoke({
FunctionName: 'getSecrets',
InvocationType: "RequestResponse",
Payload: JSON.stringify(input)
}).promise();
return result;
}
exports.handler = async function (event, context) {
var myReq = await client('Request');
console.log(myReq);
};
The 'client' does not get initialized. The same code works perfectly if executed within the handler.
initSecrets contains a lambda invocation of getSecrets() which calls the AWS SecretsManager
Has anyone an idea how asynchronous functions can be properly called for initialization purpose outside the handler?
Thank you very much for your support.
I ran into a similar issue trying to get next-js to work with aws-serverless-express.
I fixed it by doing the below (using typescript so just ignore the :any type bits)
const appModule = require('./App');
let server: any = undefined;
appModule.then((expressApp: any) => {
server = createServer(expressApp, null, binaryMimeTypes);
});
function waitForServer(event: any, context: any){
setImmediate(() => {
if(!server){
waitForServer(event, context);
}else{
proxy(server, event, context);
}
});
}
exports.handler = (event: any, context: any) => {
if(server){
proxy(server, event, context);
}else{
waitForServer(event, context);
}
}
So for your code maybe something like
var client = undefined;
initSecrets("MyWebApi").then(result => {
var secret = JSON.parse(result.Payload);
client= new MyWebApiClient(secret.API_KEY, secret.API_SECRET)
})
function waitForClient(){
setImmediate(() => {
if(!client ){
waitForClient();
}else{
client('Request')
}
});
}
exports.handler = async function (event, context) {
if(client){
client('Request')
}else{
waitForClient(event, context);
}
};
client is being called before it has initialised; the client var is being "exported" (and called) before the async function would have completed. When you are calling await client() the client would still be undefined.
edit, try something like this
var client = async which => {
var result = await initSecrets("MyWebApi");
var secret = JSON.parse(result.Payload);
let api = new MyWebApiClient(secret.API_KEY, secret.API_SECRET);
return api(which) // assuming api class is returning a promise
}
async function initSecrets(secretName) {
var input = {
"secretName" : secretName
};
var result = await lambda.invoke({
FunctionName: 'getSecrets',
InvocationType: "RequestResponse",
Payload: JSON.stringify(input)
}).promise();
return result;
}
exports.handler = async function (event, context) {
var myReq = await client('Request');
console.log(myReq);
};
This can be also be solved with async/await give Node v8+
You can load your configuration in a module like so...
const fetch = require('node-fetch');
module.exports = async () => {
const config = await fetch('https://cdn.jsdelivr.net/gh/GEOLYTIX/public/z2.json');
return await config.json();
}
Then declare a _config outside the handler by require / executing the config module. Your handler must be an async function. _config will be a promise at first which you must await to resolve into the configuration object.
const _config = require('./config')();
module.exports = async (req, res) => {
const config = await _config;
res.send(config);
}
Ideally you want your initialization code to run during the initialization phase and not the invocation phase of the lambda to minimize cold start times. Synchronous code at module level runs at initialization time and AWS recently added top level await support in node14 and newer lambdas: https://aws.amazon.com/blogs/compute/using-node-js-es-modules-and-top-level-await-in-aws-lambda/ . Using this you can make the init phase wait for your async initialization code by using top level await like so:
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
console.log("start init");
await sleep(1000);
console.log("end init");
export const handler = async (event) => {
return {
statusCode: 200,
body: JSON.stringify('Hello from Lambda!'),
};
};
This works great if you are using ES modules. If for some reason you are stuck using commonjs (e.g. because your tooling like jest or ts-node doesn't yet fully support ES modules) then you can make your commonjs module look like an es module by making it export a Promise that waits on your initialization rather than exporting an object. Like so:
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
const main = async () => {
console.log("start init");
await sleep(1000);
console.log("end init");
const handler = async (event) => {
return {
statusCode: 200,
body: JSON.stringify('Hello from Lambda!'),
};
};
return { handler };
};
# note we aren't exporting main here, but rather the result
# of calling main() which is a promise resolving to {handler}:
module.exports = main();