Skip to content

Asynchronous Components And Transitions

Kevin Mas Ruiz edited this page Jul 16, 2019 · 4 revisions

Asynchronous components enable applications to retrieve big amounts of data or process important amounts of work without blocking user interactions. Luckily, most frameworks nowadays support some kind of asynchronous processing of work. However, most of the frameworks follow an imperative approach that promotes side-effects that are usually hard to test.

The approach of morphonent is to allow any component to be rendered asynchronously without blocking other components. This allows some subtle patterns that are really powerful for designing applications:

  • There is no difference between an asynchronous and a synchronous component.
  • Side-effects will be handled by the renderer.
  • Allows transitions to work seamlessly.

How do we make an asynchronous component?

Well, there is no real difference from a synchronous component, other than the return value being wrapped in a promise. Let's write a sleep function to simulate hard work:

function sleep(ms) {
    return new Promise(r => setTimeout(r, ms))
}

Now let's create a component that will wait for six seconds and then render Hello World!.

function sayHelloWorld() {
    return sleep(6000).then(e => element('p', {}, 'Hello World!'))
}

window.onload = () => {
    renderOn('body', sayHelloWorld())
}

Now, after a few seconds, we will see how our browser renders the desired Hello World! text. However, as you noticed, we have a problem of feedback. What do we do during the asynchronous request? Probably we want to render something to give feedback to the user so they notice that the application is doing some hard word.

Here is where transitions come in handy.

Transitions

Transitions are a way to concatenate two component renderings in different times on the same position. For example, you would like to have an spinner, or a text saying that we are loading some data for the user. Using transitions is quite easy as they are just a function.

First let's start importing the transition function:

import { renderOn, element, transition } from 'morphonent'

Now let's create a component that will tell the user that some data is loading:

function weAreLoading() {
    return element('p', {}, 'Sorry to bother you, but we are loading some cool data that you will enjoy.')
}

Now we can change our hello world component to do the transition:

function sayHelloWorld() {
    const asyncComponent = sleep(6000).then(e => element('p', {}, 'Hello World!'))
    return transition(weAreLoading(), asyncComponent)
}

As you can see, at the beginning, we will see the 'loading text', but when the promise has been settled, it will automatically update to the new hello world component. Transitions allows us to build complex asynchronous compositions of components easily and without handling side-effects ourselves.

However, there are some design decisions that limit how transitions work:

  1. A transition can only be done between a synchronous component and a asynchronous one. It means that the first parameter needs to be a component, and the second one a promise of a component. The main reason to disallow both components to be asynchronous is the order of fulfillment. Two promises are not guaranteed to finish in order, so transitions could break.

  2. Transitions can only be done between two components, not multiple. Because of the same reason stated above and simplicity. However, you could use Promise.all to render when all data has been gathered, like in the following example:

function sayHelloWorld() {
    const hello = sleep(6000).then(e => element('p', {}, 'Hello World!'))
    const bye = sleep(10000).then(e => element('p', {}, 'Bye World!'))

    const all = Promise.all([hello, bye]).then(comps => element('div', {}, comps[0], comps[1]))
    return transition(weAreLoading(), all)
}

Next Steps

Clone this wiki locally