Pragmatic Pineapple 🍍

render_async 2.1.7 Lets You Retry Requests With Delay

Published Last updated loading views

In this blog post, we will explain all new features and how they came to be in the latest 2.1.7 version.

Delay

Photo by Asael Peña on Unsplash

Hey, thanks for reading about the new release, I truly appreciate it! Before we jump into details, I’d like to mention that I created a render_async Discord server. Please join us there, we are just starting! Let’s make this gem even better.

The new version has a couple of new features you can try out:

  • Retry render_async request after some time
  • Control polling by dispatching events
  • Customize content_for name

Also, our README got a brand new shine to it ✨

Let’s go over all the new features and how to use them.

Wait a bit, then retry

Before, we added a feature where you could tell render_async to retry the request if it fails. It is pretty straightforward. All you need to do is specify the number of retries you want to have. If the request is not successful after, let’s say, three attempts, the render_async will stop retrying.

Retrying is useful for some endpoints that are ‘flaky’, meaning that sometimes they return the successful response, sometimes they fail. To do this, you can write the following:

<%= render_async users_path, retry_count: 5 %>

By setting retry_count, we are telling how many times render_async should retry the request to the users_path in our case. But, for some folks, it may be useful to have some delay between the requests. For example, maybe the server you are trying to reach is overcrowded, and it could use a break from constant requests. Whatever the reason is, you can now set a retry_delay like so:

<%= render_async users_path,
                 retry_count: 5,
                 retry_delay: 2000 %>

We are giving the server a 2 seconds break before render_async makes another request to it. The logic with retrying will stay the same, meaning the render_async will quit retrying after 5th attempt.

I’m happy to hear your experiences with this feature! Consider joining the render_async Discord server and get a response quickly!

Hey, you, stop. OK, now start.

With render_async, you can easily do HTML polling without having to write JavaScript. You can do this by writing this in your view:

<%= render_async comments_path, interval: 5000 %>

render_async will then send a request to comments_path every 5 seconds. This is all nice and dandy, but how in the hell do you stop it? I am glad you asked. Before, you could control polling by interacting with an element on the page, such as a link that can stop and start polling. To do this, you can do something like so:

<a href='#' id='comments-button'>Load comments</a>

<%= render_async comments_path,
                 toggle: { selector: '#comments-button', event: :click },
                 interval: 2000 %>

The “Load comments” button will serve as a switch, toggle if you like. What you need to do is to specify what is a toggle to render_async. You can do this by passing a toggle options object with selector - an ID of the element, and event - e.g. ‘click’ if the element is a button.

In our case, whenever a user clicks the “Load comments” button, the polling will stop if started, or start if stopped. You could utilize events render_async is dispatching after each request to change the copy of the link if you want, but that’s a story for another post. Consider joining the newsletter to get new posts ASAP.

Stop, but with events

We described one way on how to control polling. The new version added the ability to control polling by dispatching events. In other words, render_async now listens for:

  • ‘async-stop’, and
  • ‘async-start’ events.

All you need to do is the following:

<%= render_async comments_path,
                 container_id: 'controllable-interval', # set container_id so we can get it later easily
                 interval: 3000 %>

render_async will poll the comments_path every 3 seconds, and it will put the response in the container with controlalble-interval ID. We will see in a second why this matters. Then, in your view, you can put two buttons - one to stop polling, another to start it.

<button id="stop-polling">Stop polling</button>
<button id="start-polling">Start polling</button>

<script>
  var container = document.getElementById("controllable-interval")
  var stopPolling = document.getElementById("stop-polling")
  var startPolling = document.getElementById("start-polling")

  var triggerEventOnContainer = function (eventName) {
    var event = new Event(eventName)

    container.dispatchEvent(event)
  }

  stopPolling.addEventListener("click", function () {
    container.innerHTML = "<p>Polling stopped</p>"
    triggerEventOnContainer("async-stop")
  })
  startPolling.addEventListener("click", function () {
    triggerEventOnContainer("async-start")
  })
</script>

We then trigger ‘async-stop’ whenever “Stop polling” is clicked, or we trigger ‘async-start’ whenever “Start polling is clicked.

💡 Please note that events need to be dispatched to a render_async container.

Controlling through events is pretty neat if you need that fine-grained control over the polling. Another improvement that can be made is to stop polling based on the response from a server or to pass in an option to start polling immediately when the toggle is set (which is not the case right now). If you are interested in any of these or have questions or feedback, consider joining the render_async Discord server!

Naming is hard

Another feature in the 2.1.7 version allows you to specify a name for the content_for where render_async code will reside. It might not make much sense to you at first, but there is a possibility to have a render_async inside render_async 🤯.

Nested render_async

You would call render_async for one endpoint, and that endpoint has another render_async call in its response. You can get into more details in the README. The new version allows you to differentiate the place where the logic for the nested render_async will reside.

<%= render_async comment_stats_path, content_for_name: :render_async_comment_stats %>

<%= content_for :render_async_comment_stats %>

The nested render_async’s logic will render in a different place than the one from the parent render_async call. In the example above, the content will render inside render_async_comment_stats path, considering you put the content_for with that name.

Anyways, if doing this cascade rendering is your thing, we have a bug that we need some help with. The problem that happens is that JavaScript doesn’t get evaluated if you use the Vanilla JS version of the gem. The jQuery version works perfectly fine because the replaceWith method from jQuery evaluates JS that you give to it. If you have any ideas on how to solve this in plain JavaScript, join our Discord, and let’s talk there.

New look, who this?

Furthermore, render_async got its README page polished a bit! Check it out here if you’d like, or admire the photo below:

New README

Do not forget to star 🌟 the project and share it with your friends and coworkers if you find it useful.

Final thoughts

Releasing a new version, polishing the README, and working on new features was a blast! Thanks for everyone that helped and keep doing the great work on contributing. I know I mentioned this a few times in the post, but please join the Discord if you are using this gem, that way we can make it even better!

P.S. 💸 If you like my work on this gem so far, and you want to give me some juice and motivation to keep improving and maintaining it, consider sponsoring me on GitHub Sponsors or through PayPal.

Also, feel free to share this on Twitter with friends and coworkers below:

Until the next one, cheers!


Nikola Đuza

Written by Nikola Đuza who helps developers improve their productivity by sharing pragmatic advice & applicable knowledge on JavaScript and Ruby. You can connect with him on Twitter.

© 2023 Nikola Đuza