Возвращение из синхронного вызова функций alert, prompt и confirm

Применительно к другим языкам программирования, если вы хотите написать процедурный код и в некоторый момент сигнализировать об использовании чего-либо, запросить ввод данных или отобразить диалоговое окно подтверждения, это не является проблемой. Но если вы пишете приложение на JavaScript, это становится немножко сложнее.

Проблема

Браузеры имеют собственные реализации функций alert, prompt и JavaScript confirm, но этого недостаточно. Они выглядят совершенно по-разному в зависимости от того, какой браузер вы используете. А также трудны в локализации и настройке.

Если вы используете среду выполнения JavaScript NW.js в более ранних операционных системах, существуют такие проблемы, как опускание кнопки «Ok» вниз диалогового окна. Поэтому на практике лучше всегда избегать встроенных функций alert, prompt и confirm. Средства контроля качества кода, такие как eslint, по умолчанию выводят предупреждение, если вы пытаетесь использовать эти функции.

Частичное решение

Было создано много библиотек, чтобы решить данную проблему. Моя любимая вне всяких сомнений – sweetalert. Это хорошо написанная, простая и гибкая библиотека, которая предоставляет предупреждения, подсказки и подтверждения. Вызвать предупреждение с помощью sweetalert легко:

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

Но существует потенциальная проблема. Любое выводимое диалоговое сообщение является обязательно асинхронным. В результате вы не можете использовать их в процедурном коде без вложенности.

Давайте представим, что вы хотели бы, чтобы пользователь ввел свое имя, Затем вы хотите посчитать гласные буквы. После этого уведомить пользователя о количестве гласных в его имени, потом спросить пользователя, желает ли он увидеть свое имя с удаленными гласными. Если он сказал да, то с помощью методов alert confirm prompt и open JavaScript вывести сообщение, чтобы показать, как будет выглядеть имя. Здесь пять простых шагов. Как бы это выглядело при помощи sweetalert с обратными вызовами?

import swal from 'sweetalert';
const vowelPatt = /[aeiou]/g;
// 1. запрашиваем у пользователя его имя 
swal({  
  title: 'Name?',
  text: 'Please enter your name.',
  type: 'input',
  showCancelButton: true,
  closeOnConfirm: false
}, name => {
  if(!name) {
    swal.close();
    return;
  }
  // 2. считаем гласные буквы
  const vowels = name.match(vowelPatt);
  const vowelCount = vowels ? vowels.length : 0;
  // 3. оповещаем пользователя
  swal({
    title: 'Vowel Count',
    text: `You have ${vowelCount} ${vowelCount === 1 ? 'vowel' : 'vowels'} in your name!`,
    closeOnConfirm: false
  }, () => {
    if(vowelCount === 0) {
      swal.close();
      return;
    }
    // 4. проверяем, хочет ли пользователь увидеть свое имя без гласных букв
    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. отображаем пользователю его имя 
      swal('Your no-vowel name!', `Your name without vowels is: ${newName}`);
    });
  });
});

Как правило, вот что вы бы сделали при написании JavaScript. Вы бы пришли к большому количеству знаков пунктуации и обратных вызовов, вложенных в обратные вызовы.

Promises

promise-alert представляет собой библиотеку, которая оборачивает sweetalert и возвращает promise-объекты, поэтому вам не нужно запускать обратные вызовы. Код, представленный выше, с использованием promise-alert (без JavaScript alert confirm) выглядел бы так:

import { promiseAlert, swal } from 'promise-alert';
const vowelPatt = /[aeiou]/g;
// 1. запрашиваем у пользователя его имя
promiseAlert({  
  title: 'Name?',
  text: 'Please enter your name.',
  type: 'input',
  showCancelButton: true,
  closeOnConfirm: false
}).then(name => {
  if(!name) {
    swal.close();
    return;
  }
  // 2. считаем гласные буквы
  const vowels = name.match(vowelPatt);
  const vowelCount = vowels ? vowels.length : 0;
  // 3. оповещаем пользователя
  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. проверяем, хочет ли он увидеть свое имя без гласных букв
    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. отображаем пользователю его имя
      promiseAlert('Your no-vowel name!', `Your name without vowels is: ${newName}`);
    });
  });
});

Несмотря на то, что я предпочитаю promise-объекты, а не обратные вызовы, признаю, что использование этого подхода не делает код лучше даже без JavaScript confirm!

Оптимальный способ

Если вы обернете свой код в функцию-генератор и запустите его с помощью библиотеки, наподобие co, то сможете писать синхронный код, который будет приостанавливаться в ожидании ввода данных пользователем.

Приведенный выше код, написанный при помощи promise-alert с функцией-генератором, будет выглядеть так:

import co from 'co';  
import { promiseAlert, swal } from 'promise-alert';
co(function* () {
  const vowelPatt = /[aeiou]/g;
// 1. запрашиваем у пользователя его имя
  const name = yield promiseAlert({
    title: 'Name?',
    text: 'Please enter your name.',
    type: 'input',
    showCancelButton: true,
    closeOnConfirm: false
  });
  if(!name) {
    swal.close();
    return;
  }
  // 2. считаем гласные буквы 
  const vowels = name.match(vowelPatt);
  const vowelCount = vowels ? vowels.length : 0;
  // 3. оповещаем пользователя
  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. отображаем пользователю его имя 
  yield promiseAlert('Your no-vowel name!', `Your name without vowels is: ${newName}`);
});

Заключение

Теперь, используя генераторы с библиотекой promise-alert, можно писать синхронный процедурный код с предупреждениями, подсказками и диалоговыми окнами подтверждения без JavaScript alert confirm!

Недостаток этого подхода заключается в том, что генераторы еще не поддерживаются во всех браузерах. Но если вы используете Babel, то уже сегодня можете написать свой код, используя генераторы, и получить скомпилированный код, соответствующий широко поддерживаемому стандарту ES5.

Или если вы пишете встроенное приложение JavaScript при помощи NW.js, то у вас уже есть доступ к генераторам без необходимости перекомпиляции.

Перевод статьи «Return of the Synchronous Alert, Prompt, and Confirm» был подготовлен дружной командой проекта Сайтостроение от А до Я.