Using JS Promises to simplify front-end conceptualization

In February 2014 my coworker Maylis started her project Copier-Créer (means Copy-Create). It’s a daily creative routine about creating images from inspiration. Her work is under a Creative Commons Licence, so feel free to use it for your craft.

Last summer, after approximately 850 days, we decided to move Copier-Créer from our portfolio to its own Website. At the time the static page for Copier-Créer was working, but it was not designed to handle that many images. This project had a simple goal: making a dynamic Website that could handle one image at a time with a partially randomized navigation depending on pictures associations by tags (topics and colors) and date.

I used Vue.js with its official router to build the front-end and Phoenix for the back-end. For the animation part, I chose the well-known GSAP library. And just for fun, I made a small desktop application with Electron to manage the content.

To prevent headaches on my side during our review and debugging sessions I tried to conceive a modular front-end. To do so, I used a lot of Promises. You can see them as a simple alternative to callbacks and events for executing asynchronous operations. For me, it made the front-end looks like a large pipeline with ramifications and interpolable parts. On top of that, I found it more readable than doing some ping-pong events/callbacks based stuff.

As I said earlier, I used GSAP for the animations. It’s entirely customizable but not promise-friendly. To quickly consume the Promise API and to turn my GSAP timelines to something that can be resolved in my chain of promises, I chose to use Q. Any callback-based functions can be set to promise like this:

function deferredTL(){
  var deferred = Q.defer()
  var tl       = new TimelineLite

  tl.to($('.wrapper-picture'), 0.25, {opacity: 1, scale: 1, ease: Cubic.easeOut})

  tl.eventCallback('onComplete', deferred.resolve)

  return deferred.promise
}

That function can be added anywhere in my promises chains. A more complex and concrete example is the first display when you open Copier-Créer:

// Start Intro loop while loading current Picture and Home Picture datas
Q.all([
  wrapperOverlayIntroWarningIn(),
  router.app.loadPicture(),
  router.app.loadHomePicture()
])
// Preload image using fetched datas
.spread((tl, home, picture) => {
  return [
    picture,
    preLoadImage(picture.file),
    tl
  ]
})
// Dispatch datas to Vue and stop Intro loop
.spread((picture, pictureObjectUrl, tl) => {
  dispatch(picture, pictureObjectUrl)

  return [
    picture,
    stopIntroWarningLoop(tl)
  ]
})
// Remove the overlay using the main color of the current Picture
.spread((picture) => {
  return wrapperOverlayIntroWarningOut(picture)
})

Time has passed since I developed the JS for this Website, I had to review it for writing this article. Now I want to rewrite a lot of things because they are old, but I still get why I wrote this instead of that. So I think the original purpose of using promises to make my code modular and readable is still valid nowadays 👍

David Authier

David Authier
Développeur freelance