Return of the Synchronous Alert, Prompt, and Confirm

When it comes to other programming languages, if you want to write procedural code and at some point alert the use of something, prompt for input, or throw up a confirm dialog, it is not that a problem. But, if you are writing your application using JavaScript this becomes a bit more difficult.

The Problem

Browsers do have native implementations of alert, prompt, and confirm, but these are sadly lacking. First, they look completely different depending on which browser you are using. Second, localization and configurability are a problem. Third, if you are using a JavaScript runtime like NW.js in an older operating system, there are problems such as the ok button hanging off the bottom of the dialog. That is not ok!

As a result, best practices are always to avoid the native alert, prompt, and confirm. Linters such as eslint by default even throw up warnings if you try and use them.

The Partial Solution

Many great libraries have been created to provide solutions to this problem. My favorite by far is sweetalert. Sweetalert is a very well made, straightforward, and flexible library which provides alerts, prompts, and confirms. To call an alert using sweetalert is as easy as this:

swal('Oops!', 'There was a problem.', 'error');

I like that! BUT, there is a potential problem with this. Any alert, confirm, or prompt is necessarily asynchronous. As result, you cannot use these in procedural code without nesting.

Let us imagine you wanted the user to 1) input their name, then 2) you wanted to count the vowels, then 3) you want to alert the user to the number of vowels in their name, then 4) you want to ask the user if they would like to see their name with all vowels removed, then 5) if they said ok, then pop up an alert to show what that would look like. These are five straightforward steps. What would that look like using sweetalert with callbacks?

import swal from 'sweetalert';

const vowelPatt = /[aeiou]/g;

// 1. prompt the user for their name
swal({
  title: 'Name?',
  text: 'Please enter your name.',
  type: 'input',
  showCancelButton: true,
  closeOnConfirm: false
}, name => {
  if(!name) {
    swal.close();
    return;
  }

  // 2. count the vowels
  const vowels = name.match(vowelPatt);
  const vowelCount = vowels ? vowels.length : 0;

  // 3. alert the user
  swal({
    title: 'Vowel Count',
    text: `You have ${vowelCount} ${vowelCount === 1 ? 'vowel' : 'vowels'} in your name!`,
    closeOnConfirm: false
  }, () => {

    if(vowelCount === 0) {
      swal.close();
      return;
    }

    // 4. check if they want to see their name w/o vowels
    swal({
      title: 'Continue?',
      text: 'Would you like to see your name with all vowels removed?',
      showCancelButton: true,
      closeOnConfirm: false
    }, confirmed => {

      if(!confirmed) return;

      const newName = name.replace(vowelPatt, '');

      // 5. show the user their name
      swal('Your no-vowel name!', `Your name without vowels is: ${newName}`);

    });

  });
  
});

Traditionally, that is what you would do when writing JavaScript. You would end up with lots of punctuation and callbacks nested inside of callbacks.

Promises

promise-alert is an npm library which wraps sweetalert and returns Promises so you do not have to pass in callbacks. Writing the above code using promise-alert would look like this:

import { promiseAlert, swal } from 'promise-alert';

const vowelPatt = /[aeiou]/g;

// 1. prompt the user for their name
promiseAlert({
  title: 'Name?',
  text: 'Please enter your name.',
  type: 'input',
  showCancelButton: true,
  closeOnConfirm: false
}).then(name => {
  if(!name) {
    swal.close();
    return;
  }

  // 2. count the vowels
  const vowels = name.match(vowelPatt);
  const vowelCount = vowels ? vowels.length : 0;

  // 3. alert the user
  promiseAlert({
    title: 'Vowel Count',
    text: `You have ${vowelCount} ${vowelCount === 1 ? 'vowel' : 'vowels'} in your name!`,
    closeOnConfirm: false
  }).then(() => {

    if(vowelCount === 0) {
      swal.close();
      return;
    }

    // 4. check if they want to see their name w/o vowels
    promiseAlert({
      title: 'Continue?',
      text: 'Would you like to see your name with all vowels removed?',
      showCancelButton: true,
      closeOnConfirm: false
    }).then(confirmed => {

      if(!confirmed) return;

      const newName = name.replace(vowelPatt, '');

      // 5. show the user their name
      promiseAlert('Your no-vowel name!', `Your name without vowels is: ${newName}`);

    });

  });
  
});

While I prefer Promises to callbacks, even I have to admit that using Promises does not make this bit of code any better!

A Much Better Way

While you can use promise-alert and directly handle the promises yourself, there is a much better way. If you wrap your code in a generator function and run it using a library like co, you can write synchronous-looking code which will pause for user input when necessary.

Writing the above code using promise-alert within a generator function would look like this:

import co from 'co';
import { promiseAlert, swal } from 'promise-alert';

co(function* () {

  const vowelPatt = /[aeiou]/g;

  // 1. prompt the user for their name
  const name = yield promiseAlert({
    title: 'Name?',
    text: 'Please enter your name.',
    type: 'input',
    showCancelButton: true,
    closeOnConfirm: false
  });

  if(!name) {
    swal.close();
    return;
  }

  // 2. count the vowels
  const vowels = name.match(vowelPatt);
  const vowelCount = vowels ? vowels.length : 0;

  // 3. alert the user
  yield promiseAlert({
    title: 'Vowel Count',
    text: `You have ${vowelCount} ${vowelCount === 1 ? 'vowel' : 'vowels'} in your name!`,
    closeOnConfirm: false
  });

  if(vowelCount === 0) {
    swal.close();
    return;
  }

  const confirmed = yield promiseAlert({
    title: 'Continue?',
    text: 'Would you like to see your name with all vowels removed?',
    showCancelButton: true,
    closeOnConfirm: false
  });

  if(!confirmed) return;

  const newName = name.replace(vowelPatt, '');

  // 5. show the user their name
  yield promiseAlert('Your no-vowel name!', `Your name without vowels is: ${newName}`);

});

Conclusion

Using generators with promise-alert you can now write synchronous, procedural code with alert, prompt, and confirm dialogs! You no longer need to have deeply nested callbacks or Promises with your procedural code spread out across multiple callbacks. The drawback is that generators are not yet supported in all browsers, BUT if you use Babel you can write your code today using generators and have it transpiled down to broadly-supported ES5. Or, if you are writing a native JavaScript application using NW.js (as I currently am), then you already have access to generators without having to transpile.

Synchronous alerts, prompts, and confirms have returned!!!