DEV Community

loading...
Cover image for The SHAME ON ME fix

The SHAME ON ME fix

brunodrugowick profile image Bruno Drugowick Updated on ・2 min read

I had a problem. On my 2-week journey into Javascript inspired by the Omnistack Week (a week where you implement a full application with NodeJS, React and React Native), I built this application but something was bothering me. I'll explain...

The function that uploads the post to the server, after doing so, redirects the user to the app's feed. Here's the code:

handleSubmit = async e => {
    // Prevents the browser from doing whatever after submiting a form
    e.preventDefault();
    // This disables the submit button while the post is being created.
    this.setState({ loading: true });

    // Creates the form to be submitted to the backend (from the app's state).
    const data = new FormData();
    data.append('image', this.state.image);
    data.append('author', this.state.author);
    data.append('place', this.state.place);
    data.append('description', this.state.description);
    data.append('hashtags', this.state.hashtags);

    // Posts to the backend.
    await api.post('posts', data).then(() => {
        // Not necessary because I redirect, but re-enables the button.
        this.setState({ loading: false });
    });

    // Redirects to the feed.
    this.props.history.push('/');
}

But, when redirected, the user would see this:

the bug

On the first version of the app, in which the image was uploaded to the server, I didn't have this problem. But then I changed to upload the image to Amazon S3, which makes more sense than storing on the application's server (and solved the problem of hosting on the free tier of Heroku, that turns everything off after a while and back on when someone hits the server).

I guess that that problem had something to do with the time it takes Amazon S3 to provision everything... but I really don't know. So, I thought I could test the link before redirecting the user, while a loading layer or something like that would have the user waiting. I thought about a progress bar within the 'Share' button or a cool loading layer above everything...

BUT, here's what I did. A function:

sleep(milliseconds) {
    var start = new Date().getTime();
    for (var i = 0; i < 1e7; i++) {
        if ((new Date().getTime() - start) > milliseconds){
            break;
        }
    }
}

And right before redirecting the user, I added this (with the comment and everything):

/**
* SHAME ON ME.
* 
* The first request to S3 after redirecting to the feed fails.
* This is my fix to it. The correct fix would be to test the URL and once it
* is working, redirect to the Feed.
* 
* For now, sit tight for about one second and hope for the best.
*/
this.sleep(1000);

You can actually see the code on this commit and, of course, contribute, since it's an open source project.

Shame on me, I know. =|

Discussion

pic
Editor guide
Collapse
marceloadsj profile image
Marcelo Junior

Hey my friend, let me try to help you with this problem.

Checking the code on github, I think the problem is not on the frontend, but here:
github.com/brunodrugowick/omnistac...

You have a kind of race condition problem on that code:

async store(req, res) {
  // some things here...

  await s3.putObject(s3Params, (error, data) => {
    if (error) {
      console.error(error);
      console.error("Error uploading to S3.")
      return res.status(500).send("An error happened (s3 upload error).");
    } else {
      data.url = s3Url;
      console.log(data);
    }
  });

  // other things there...

  return res.json(post);
}

The s3.putObject don't return a promise, so the "await" there is useless.
What happen is:

  • if the bottom part of the function (save the post on db) was faster than the AWS upload, your endpoint returns (on res.json) before the AWS upload finished (inside the callback on putObject).
  • if the bottom part takes to much time, then the AWS upload finishes and your endpoint just works.

So, to avoid that problem, maybe you can just switch for the promised version of putObject function, or move the bottom part of the code inside of the callback on putObject, so it will wait the upload, then save the post, and finally, return the response for the frontend.

The AWS SDK showing how to use promises instead of callback

Look at line 6 on . I'm not sure it's the most up to date version, but, we can try, haha.
docs.aws.amazon.com/sdk-for-javasc...

Using the promise version of the sdk allow you run unrelated things in parallel with Promise.all for ex. This can be done to improve your endpoint to be faster, but then you need to take care with failures/rollbacks as well, or even retries.

Let me know if it can help you. :D

Collapse
brunodrugowick profile image
Bruno Drugowick Author

Hello, Marcelo.

Thank you very much, my friend! I'll test it in a bit...