David Moll
David Moll

Reputation: 129

Axios post-request doesn't send complete object

I need to send an object including a base64-encoded image to an API-endpoint in TS. I use this code to convert a file to Base64 and attach it to an object:

const file = values.beleg1;
                const reader = new FileReader();
                reader.readAsDataURL(file);
                reader.onload = async function () {
                    const result = reader.result.toString().split(',')[1];
                    await valuesObj.push({ name: 'base64Image', value: result });

                };
                reader.onerror = function (error) {
                    console.log('Error: ', error);
                };

If I then log the object 'valuesObj' it does show that the base64-object is attached to the main-object. Screenshot of the logged object

However when I then I then use this object in the request like in the code below it is not shown in the payload when looking at the network-request

axios({
                url: 'http://localhost:8080/createPDF',
                method: 'POST',
                headers: {
                    'Content-Type': 'multipart/form-data',
                },
                responseType: 'blob',
                data: valuesObj,
            })
                .then((response: AxiosResponse) => {
                    setLoadingText('');
                    const blob: Blob = new Blob([response.data], { type: 'application/pdf' });
                    const objectUrl: string = window.URL.createObjectURL(blob);
                    window.open(objectUrl);
                })
                .catch((error) => {
                    console.log(error);
                });

Screenshot of the network-payload

What is the reason for this? Does it have anything to do with the length/size of the string? Or is there another reason?

Upvotes: 1

Views: 1505

Answers (2)

Hangover Sound Sound
Hangover Sound Sound

Reputation: 192

Your Problem seems to be that your async axios request gets the value of your valuesObj, before onload actually adds your new record.

Sorry forgot a solution ^^

Either you put your axios request directly into the pipe...

reader.onload = 
        (async function () {
            const result = reader.result.toString().split(',')[1]
            await valuesObj.push({ name: 'base64Image', value: result})
            return valuesObj
        })
        .then( 
            data =>
            axios({
                url: 'http://localhost:8080/createPDF',
                method: 'POST',
                headers: {
                    'Content-Type': 'multipart/form-data',
                },
                responseType: 'blob',
                data,
            })
            .then((response: AxiosResponse) => {
                setLoadingText('')
                const blob: Blob = new Blob([response.data], { type: 'application/pdf' })
                const objectUrl: string = window.URL.createObjectURL(blob)
                window.open(objectUrl)
            })
            .catch(console.log))
        )

})

or you create another async function which you can call from your onload or from another async fn, which awaits both async events.

Upvotes: 0

siride
siride

Reputation: 209625

What is happening is the reader hasn't finished reading before the Axios call proceeds. The reader executes asynchronously. Thus, the order of operations often ends up something like this:

  1. Start the read operation.
  2. Attach event handler onloaded.
  3. Start the Axios request, which is probably sent immediately at this point.
  4. Complete the read operation.
  5. Complete the Axios request.

Because you aren't waiting for the load event to finish before sending the Axios request, the request does not include the new data. You need to make sure the load event is finished and the data is pushed onto your array before you proceed with the Axios call.

try {
    const file = values.beleg1;
    const reader = new FileReader();
    reader.readAsDataURL(file);

    // construct a promise that will resolve once loading is complete,
    //   and then wait for it, so that we don't make the axios call until
    //   after the loading is done.
    await new Promise<void>((resolve, reject) => {
        reader.onload = function () {
            const result = reader.result.toString().split(',')[1];
            valuesObj.push({ name: 'base64Image', value: result });
            // notify that the promise has completed its work
            resolve();
        };

        reader.onerror = function () {
            // notify that the promise failed and pass along the error object
            reject(reader.error);
        };
    });

    // I picked any since I don't want to define out the full type, but you probably should
    const response: any = await axios({
        url: 'http://localhost:8080/createPDF',
        method: 'POST',
        headers: {
            'Content-Type': 'multipart/form-data',
        },
        responseType: 'blob',
        data: valuesObj,
    });

    setLoadingText('');
    const blob: Blob = new Blob([response.data], { type: 'application/pdf' });
    const objectUrl: string = window.URL.createObjectURL(blob);
    window.open(objectUrl);
} catch (error) {
    // all errors during the processing are caught in one place
    console.log('Error: ', error);
}

Note that I put everything in async/await style, except the required Promise object, and I have a single try-catch block. If you want to process errors at a higher level of granularity, split it into multiple try-catch blocks.

When we make sure to await every asynchronous action, we ensure that we don't start the next one until we are done with the previous, so everything is done in order.

Upvotes: 2

Related Questions