JavaScript Promises

Defined by MDN,

A Promise is a proxy for a value not necessarily known when the promise is created. It allows you to associate handlers with an asynchronous action’s eventual success value or failure reason. This lets asynchronous methods return values like synchronous methods: instead of immediately returning the final value, the asynchronous method returns a promise to supply the value at some point in the future.

It has three states:

  • pending: initial state, neither fulfilled nor rejected.
  • fulfilled: meaning that the operation completed successfully.
  • rejected: meaning that the operation failed.

Intro

A promise is basically a wrapper around a value that may or may not be known when the object is first instantiated (created), and provides a method for handling a value after it is known (resolved), or when it’s unavailable on failure (rejected).

Breakdown

Let’s look at some code:

function getCurrentTime(onSuccess, onFail) {
  // Get the current 'global' time from an API using Promise
  return new Promise((resolve, reject) => {
    setTimeout(function() {
      // randomly decide if the date is retrieved or not
      var didSucceed = Math.random() >= 0.5;
      didSucceed ? resolve(new Date()) : reject('Error');
    }, 2000); // delay by 2000, 2 seconds before responding.
  })
}

Here we have a function getCurrentTime that has two callbacks, onSuccess & onFail. Those are faciliated by creating a Promise object with two states (resolve, reject) which it feeds back to the parent by the return.

We use setTimeout and a random generator to flip between resolve and reject for our example.

var didSucceed = Math.random() >= 0.5;

Then we use a one-line if statement to return a success (resolve) or fail (reject) state to the parent function.

didSucceed ? resolve(new Date()) : reject('Error');

Then as we want to simulate an API statement we add a delay of 2 seconds.

setTimeout(function() {
  // ... code ...
}, 2000); // delay by 2000, 2 seconds before responding.

Usage

Now how to use it?

To catch the value on success, we’ll use the then() function available on the Promise instance object. The then() function is called with whatever the return value is of the promise itself. For instance, in the example above, the getCurrentTime() function resolves with the currentTime() value (on successful completion) and calls the then() function on the return value (which is another promise) and so on and so forth.

To catch an error that occurs anywhere in the promise chain, we can use the catch() method.

getCurrentTime()
  .then(currentTime => getCurrentTime())
  .then(currentTime => {
    console.log('The current time is: ' + currentTime);
    return true;
  })
  .catch(err => console.log('There was an error:' + err))

.then Breakdown

So when we do,

.then(currentTime => getCurrentTime())

The return value is the current time (onSuccess), that’s then passed to the next then() in the chain.

.then(currentTime => {
  console.log('The current time is: ' + currentTime);
  return true;
})

Which takes that value currentTime and prints it to the console.

The important thing to remember is each time we call .then() it operates on the previously returned value in the chain. So if we do something with the previous value the next .then() call will be handed that value, and so on.

fetch() method & React

Moving on, the fetch() method supports Promises so we can wire in a call to a backend API and handle it similarly:

fetch("/api/getuser")
  .then(resp => resp.json())
  .then(resp => {
    const fullname = resp.fullname;
    this.setState({ fullname: fullname })
  })

First we make a call to /api/getuser that returns:

{
  id: "424324234343ffer3",
  fullname: "John Smith"
}

We get the json() response then pass it to the next .then() call, take that json and pull out the fullname into a constant and then using setState() set the value of the fullname local object.

References

Many thanks to these articles, especially Ari Lerner for his great articles linked here under Full Stack React.

Webpack 4 & Babel 7 Setup

Last year I had to upgrade Webpack to 4 and Babel to 7, which even though it sounds trivial the upgrade included a lot of breaking changes. The new mode option amalgamating your production and development setup into one, switching libraries as some did not make the transition to 4 and a general reworking of how I laid out the original into what is presented below.

After which I’ll break down what each component does.

webpack.config.js
// webpack.config.js
const path = require('path');

const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CopyWebpackPlugin = require('copy-webpack-plugin');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
const WorkboxPlugin = require('workbox-webpack-plugin');

const devMode = process.env.NODE_ENV !== 'production';

const BUILD_DIR = path.resolve(__dirname, 'build');
const SRC_DIR = path.resolve(__dirname, 'src');

const isHot = path.basename(require.main.filename) === 'webpack-dev-server.js';

console.log(isHot)

console.log('BUILD_DIR', BUILD_DIR);
console.log('SRC_DIR', SRC_DIR);

const cssPlugin = new MiniCssExtractPlugin({
  filename: "style.css"
});

const htmlPlugin = new HtmlWebpackPlugin({
  template: "./public/index.html",
  filename: "./index.html"
});

const copyWebpack = new CopyWebpackPlugin([
    {from: './public/images', to: 'images'},
    {from: './public/fonts', to: 'fonts'},
    {from: './node_modules/font-awesome/fonts', to: 'fonts'},
    {from: './public/images/favicon.ico' },
    {from: './public/images/apple-icon.png' },
    {from: './public/robots.txt' }
  ],
  {copyUnmodified: false}
);

const uglifyJs = new UglifyJsPlugin({
  parallel: 4
});

const workbox = new WorkboxPlugin.GenerateSW({
  // these options encourage the ServiceWorkers to get in there fast
  // and not allow any straggling "old" SWs to hang around
  clientsClaim: true,
  skipWaiting: true
})

module.exports = {
    target: 'web',
    entry: {
      index: [SRC_DIR + '/index.js']
    },
    output: {
      publicPath: '/',
      path: BUILD_DIR,
      filename: '[name].bundle.js'
    },
    devtool: 'source-map',
    devServer: {
      port: 3000,
      disableHostCheck: true,
      host: 'localhost',
      contentBase: BUILD_DIR,
      historyApiFallback: true,
      compress: true,
      hot: true,
      open: true,
      proxy: {
        '/api/*': 'http://localhost:5000',
        '/media/*': 'http://localhost:5000'
      }
    },
    module : {
        rules : [
            {
                test: /\.s?[ac]ss$/,
                use: [
                    isHot ? "style-loader" : MiniCssExtractPlugin.loader,
                    { loader: 'css-loader', options: { url: false, sourceMap: true } },
                    { loader: 'sass-loader', options: { sourceMap: true } }
                ],
            },
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: {
                  loader: "babel-loader"
                }
            }
        ]
    },
    plugins: this.mode === 'production'
     ? [cssPlugin, htmlPlugin, copyWebpack, workbox, uglifyJs]
     : [cssPlugin, htmlPlugin, copyWebpack, workbox]
    ,
    mode : devMode ? 'development' : 'production'
};
.babelrc
// .babelrc
{
  "presets": [
    "@babel/preset-react",
    [ "@babel/preset-env", {
      "targets": {
        "browsers": [
          ">0.25%",
          "not op_mini all"
        ]
      }
    }]
  ],
  "plugins": [
    "@babel/plugin-proposal-object-rest-spread",
    "@babel/plugin-proposal-class-properties",
    "@babel/plugin-transform-runtime"
  ]
}

Breakdown

Build & Src Directories

First I define constants to store where the source is (/src) and compiled build (via webpack-dev-tools & webpack, in /build), we’ll need those later.

const BUILD_DIR = path.resolve(__dirname, 'build');
const SRC_DIR = path.resolve(__dirname, 'src');

Hot Loading

For hot reloading, when the code changes refresh the client, I had a problem with MiniCssExtractPlugin not handling it. So I setup a constant that if dev-server is loaded it will run the appropriate styling libraries.

const isHot = path.basename(require.main.filename) === 'webpack-dev-server.js';

use: [
    isHot ? "style-loader" : MiniCssExtractPlugin.loader,
    { loader: 'css-loader', options: { url: false, sourceMap: true } },
    { loader: 'sass-loader', options: { sourceMap: true } }
],

CSS & HTML Plugins

Next defining the css extractor and html plugins, again defining the setup inside a constant. A really handy method that cleans up how they’re implemented later on under ‘plugins’.

const cssPlugin = new MiniCssExtractPlugin({
  filename: "style.css"
});

const htmlPlugin = new HtmlWebpackPlugin({
  template: "./public/index.html",
  filename: "./index.html"
});
plugins: this.mode === 'production'
 ? [cssPlugin, htmlPlugin, copyWebpack, workbox, uglifyJs]
 : [cssPlugin, htmlPlugin, copyWebpack, workbox]
,

I don’t use uglifyjs in development as there’s no need, and it’ll massively slow down hot-loading.

CopyWebpackPlugin

For CopyWebpackPlugin I setup the usual copy definitions for images, fonts and font-awesome so everything’s self hosted.

Then for favicons and the robots.txt file which usually reside in the root I set the from but not the to so it will default to copying to the root location of the website (correct me if i’m wrong).

const copyWebpack = new CopyWebpackPlugin([
    {from: './public/images', to: 'images'},
    {from: './public/fonts', to: 'fonts'},
    {from: './node_modules/font-awesome/fonts', to: 'fonts'},
    {from: './public/images/favicon.ico' },
    {from: './public/images/apple-icon.png' },
    {from: './public/robots.txt' }
  ],
  {copyUnmodified: false}
);

copyUnmodified’s default value is false which: “Copies files, regardless of modification when using watch or webpack-dev-server. All files are copied on first build, regardless of this option”.

uglifyJs

Next setup uglifyJs, this obfuscates your end result making it difficult for others to reverse engineer.

As I usually have more than one core on my computer I set it up to use parallel processing using 4 threads as this is a very cpu intensive task.

const uglifyJs = new UglifyJsPlugin({
  parallel: 4
});

WorkboxPlugin

WorkboxPlugin is a tool to implement service workers inside your web app and enable caching so your application has the chance to operate even when in offline mode and then gracefully connect when back online.

const workbox = new WorkboxPlugin.GenerateSW({
  // these options encourage the ServiceWorkers to get in there fast
  // and not allow any straggling "old" SWs to hang around
  clientsClaim: true,
  skipWaiting: true
})

You can read more here https://webpack.js.org/guides/progressive-web-application/#adding-workbox.

devServer

While in development I have the server running a separate process on port 5000 with the client at 3000. So I setup proxy’s for the /api and /media uploads.

I enable compression, hot loading and set the hostname. With that as so as the client starts up an event will be fired to the browser to hot-load the site on http://localhost:3000

devServer: {
  port: 3000,
  disableHostCheck: true,
  host: 'localhost',
  contentBase: BUILD_DIR,
  historyApiFallback: true,
  compress: true,
  hot: true,
  open: true,
  proxy: {
    '/api/*': 'http://localhost:5000',
    '/media/*': 'http://localhost:5000'
  }
},

modes

With WebPack 4 it introduced the concept of build modes so we only had one webpack file to maintain.

I generate a constant that holds the current mode taken from the current process.env.NODE_ENV node environment.

const devMode = process.env.NODE_ENV !== 'production';

Which I can then pass over to mode, plugins and what else needs it.

plugins: this.mode === 'production'
 ? [cssPlugin, htmlPlugin, copyWebpack, workbox, uglifyJs]
 : [cssPlugin, htmlPlugin, copyWebpack, workbox]
,
mode : devMode ? 'development' : 'production'

Babel

For Babel I had to change things with version 7.

// .babelrc 6
{
  "presets": [
    "react",
    "env"
  ],
  "plugins": [
    "transform-object-rest-spread",
    "transform-class-properties",
    "transform-runtime"
  ]
}

Still loading support for spread operator (@babel/plugin-proposal-object-rest-spread), class properties (@babel/plugin-proposal-class-properties) and transform which adds common helpers across your compiled app that are deduplicated to help with code size (@babel/plugin-transform-runtime)

// .babelrc 7
{
  "presets": [
    "@babel/preset-react",
    [ "@babel/preset-env", {
      "targets": {
        "browsers": [
          ">0.25%",
          "not op_mini all"
        ]
      }
    }]
  ],
  "plugins": [
    "@babel/plugin-proposal-object-rest-spread",
    "@babel/plugin-proposal-class-properties",
    "@babel/plugin-transform-runtime"
  ]
}

The juicy part is:

"@babel/preset-react",
[ "@babel/preset-env", {
  "targets": {
    "browsers": [
      ">0.25%",
      "not op_mini all"
    ]
  }
}]

Which rather than compile to support all browsers which increases code size, it limits support to the top 25% of all current browsers.

Removing support for opera mini on all versions.

not op_mini all

This is a browserlist query, you can read more about it here https://github.com/browserslist/browserslist.

The GROW Model

I was introduced to the GROW model on my first cohort with the Mozilla Open Leaders program using it as a method of setting goals and navigating the problems you experience when trying to achieve those goals.

Originally developed in the UK, it was used extensively in corporate coaching in the late 1980’s and 90’s. We used as a framework for meeting and is broken into five parts.

G. Goal

First establish the end point, where the client wants to be in such a way that is is clear when you have reached that point. 

To reference Avengers this would be when Thanos is defeated and everyone who died in the snap is resurrected. What good about this is it’s clear and easy to identify when that point has been reached.

R. Reality

The reality is where the client is now. What are the issues, the challenges and how far away are they from the goal?. 

To get an unbiased opinion this should come from the client but you can help by asking questions to make sure the reality is fully explored.

  • What is happening now? 
  • What is the effect or result of this?
  • Have you already taken any steps towards your goal?
  • Does this goal conflict with any other goals or objectives?

So continuing our Avengers example, what is our reality? Half the universe has been wiped out, no one knows if they can be brought back and the glove was destroyed.

O. Obstacles

The third part can be broken down into two sides, Obstacles and Options. 

These obstacles are stopping the clients from getting to where they want to be,

  • What are they?
  • Have they had success in the past, what made the difference?
  • What might you need to change in order to achieve the goal? 

In reality if there were no obstacles, then they’d already be at their goal.

Diving back to the movies, the steps they already took were to bring the fight to Thanos on the world of Titan but that wasn’t the grand success they hoped for. What were the obstacles in their way? What could have the done differently to achieve their goal?

O. Options

Now we’ve identified our Goal, Reality and Obstacles what are our Options? What things can we do to get to our Goal?

Things you can do here are,

  • Take time to brainstorm the full range of options.
  • Offer suggestions carefully, remember you don’t want to be seen as the expert in their situation you’re more trying to unlock valuable nuggets of information that the client might have overlooked that’s available to them.
  • Are there any constraints we can remove?
  • Are we repeating some action without noticing that we could potentially remove?

Once we’re happy with the options available to us we can move onwards.

I understandably don’t know what happens next with Avengers. However that doesn’t mean we can’t break down what their options are and make sure they don’t repeat those same mistakes again.

W. Way Forward

So now we’ve identified the obstacles and uncovered our options we can move on to deciding on a path forward.

We can go ahead and convert those options to actions points, attach time frames to the relevant ones and start building a plan to getting to our end goal.

Things we can do,

  • Commit to action.
  • Set a long-term aim if appropriate.
  • Make specific steps and define timing.
  • Ensure choices are made.

Epilogue

One of the takeaways from this process is to understand that it’s really focused on asking questions. 

You don’t have to be an expert in their situation, you’re more of a facilitator in the conversation.

References