Объясните асинхронное программирование на Javascript на уроке истории.

С самого начала своей карьеры я обучал многих новых разработчиков и случайно понял, что все новые разработчики NodeJS/Javascript редко понимают природу асинхронного программирования в NodeJS и Javascript. Здесь я имею в виду его природу, понимание того, как это работает в реальной жизни, а не объяснение этого с помощью цикла событий и абстрактных концепций, которые сбивают с толку других.

Почему мы используем асинхронность?

Представьте себе один день, когда вы просыпаетесь прекрасным ранним утром и готовы начать рабочий день. Ставишь чайник, ждешь, пока вода закипит. Через 3 минуты вы обнаружили, что теряете кучу времени, поскольку тем временем вам следовало приготовить кофе. И вы жалеете, что не сделали это асинхронно. Когда я изучал PHP, я заставил себя думать как однопоточная машина. Мы подумали, что нам нужно выучить язык программирования для работы с машинами. Мы ошибались, машины были созданы, чтобы служить людям, учиться у людей, и если мы можем действовать асинхронно, то и машины тоже должны это делать.

История асинхронного программирования.

Я люблю историю. Я всегда учусь на прошлом, чтобы понять эволюцию людей и вещей. Как только мы поймем историю, мы сможем понять ее природу, как только мы это сделаем, мы поймем ее суть.

С самого начала программирования была введена концепция обратного вызова, поэтому первая версия Javascript уже имела обратный вызов в своем ядре. Позже мы адаптировали jQuery как основу всей сети. Мы так хорошо познакомились с этим фрагментом кода jQuery:

$("button").click(function(){
  $("p").hide("slow", function(){
    alert("The paragraph is now hidden");
  });
});

Кажется, все идет хорошо, пока у нас не будет тысячи строк кода с одним файлом Javascript, полным ада обратных вызовов и комментариев, в надежде, что комментарии волшебным образом объяснят этот беспорядок.

fs.readdir(source, function (err, files) {
  if (err) {
    console.log('Error finding files: ' + err)
  } else {
    files.forEach(function (filename, fileIndex) {
      console.log(filename)
      gm(source + filename).size(function (err, values) {
        if (err) {
          console.log('Error identifying file size: ' + err)
        } else {
          console.log(filename + ' : ' + values)
          aspect = (values.width / values.height)
          widths.forEach(function (width, widthIndex) {
            height = Math.round(width / aspect)
            console.log('resizing ' + filename + 'to ' + height + 'x' + height)
            this.resize(width, height).write(dest + 'w' + width + '_' + filename, function(err) {
              if (err) console.log('Error writing file: ' + err)
            })
          }.bind(this))
        }
      })
    })
  }
})

Наш код постепенно смещается вправо и исчезает с экрана………………………………………………………………………………………… …………….. и пока не найден.

Вы можете взглянуть на этот фрагмент кода, выполняющий пример кода с обратными вызовами:

/**
 * @description This is a example with 3 layer callback hell
 */

/**
 * Fake like we getting data from API which takes 1s
 *
 * @param callback
 */
function getData(callback) {
  setTimeout(() => {
    const data = [
      {id: 1, name: "Developer A"},
      {id: 2, name: "Devloper B"}
    ]; // Assume the data from API

    callback(data);
  }, 1000)
};

/**
 * Fake like we are submitting the data to an API elsewhere
 *
 * @param inputData
 * @param callback
 */
function submitData (inputData, callback) {
  console.log('starting to submit');
  setTimeout(() => {
    // Act like we submit the inputData to the server and takes 1s
    const status = randomStatus();
    callback({
      success: status
    });
  }, 1000)
}

/**
 * Just a helper random the result of the submit: success or failed
 *
 * @returns {boolean}
 */
const randomStatus = () => [true, false][Math.floor(Math.random() * 2)];

// Main body of the example
getData((data) => {
  const names = data.map(data => data.name);
  console.log(names)
  submitData(names, (response) => {
    const {success} =  response;
    if (success === true) {
      console.log("This is a successful form")
    } else {
      console.log("This is a failed form")
    }
  })
});

/**
 * @conclusion
 *
 * Just imagine that after submit the form,
 * we continue to do more async processes
 * and the code keeps on being nested
 *
 * @see See this link for closer look at callback hell
 * @link http://callbackhell.com/
 *
 */

Обещания — Мы обещаем сделать программирование лучше

Когда ад обратных вызовов так сильно обрушился на нас на протяжении многих лет, в 2015 году мы познакомились с совершенно новой вещью под названием Promise. Так что же такое Promise?

Точно так же, как это называется. Обещание. Когда кто-то дает вам обещание, это что-то в будущем, оно может добиться успеха или потерпеть неудачу, не совсем уверен в том, сколько времени это займет. Точно так же, как вы покупаете облигацию, вы получаете на бумаге обещание, что вы вернете свои деньги через 10 лет под 10 процентов.

Но как бы мы написали этот фрагмент выше в Promises? Это было бы так:

/**
 * Asynchronous programming with Promise
 */
function getData() {
  return new Promise((resolve, reject) => {
    const data = [
      {id: 1, name: "Developer A"},
      {id: 2, name: "Devloper B"}
    ];

    setTimeout(() => {
      resolve(data);
    }, 1000);
  });
}

/**
 * Fake like we are submitting the data to an API elsewhere
 *
 * @param inputData
 */
function submitData (inputData) {
  console.log('starting to submit');

  return new Promise((resolve, reject) => {
    // Act like we submit the inputData to the server and takes 1s
    const status = randomStatus();

    setTimeout(() => {
      resolve({
        success: status
      });
    }, 1000);
  });
}

/**
 * Just a helper random the result of the submit: success or failed
 *
 * @returns {boolean}
 */
const randomStatus = () => [true, false][Math.floor(Math.random() * 2)];

// Main body of the example
getData().then(data => {
  const names = data.map(data => data.name);
  console.log(names);
  submitData(names).then(response => {
    const {success} =  response;
    if (success === true) {
      console.log("This is a successful form")
    } else {
      console.log("This is a failed form")
    }
  });
});

Ой! Новое вино в старые бутылки. Несмотря на то, что мы получили новый синтаксис, мы все равно страдали от ада обратных вызовов. Честно говоря, Promises решили множество проблем: благодаря им NodeJS и Javascript стали гораздо чище справляться с асинхронным программированием уже благодаря интуитивно понятным API. Не хватает одного: он еще не превратил наш программный код в учебник или сценарий логики программирования на простом английском языке, чтобы люди могли легко его переварить.

Синтаксис асинхронности/ожидания

У прекрасного пончика не хватает посыпки. В 2017 году, вскоре после появления Promise, нам на помощь пришел синтаксис async/await. С помощью async/await мы наконец можем интуитивно переписать приведенный выше код, как показано ниже, строка за строкой.

**
 * Asynchronous programming with Promise
 */
function getData() {
  return new Promise((resolve, reject) => {
    const data = [
      {id: 1, name: "Developer A"},
      {id: 2, name: "Devloper B"}
    ];

    setTimeout(() => {
      resolve(data);
    }, 1000);
  });
}

/**
 * Fake like we are submitting the data to an API elsewhere
 *
 * @param inputData
 */
function submitData (inputData) {
  console.log('starting to submit');

  return new Promise((resolve, reject) => {
    // Act like we submit the inputData to the server and takes 1s
    const status = randomStatus();

    setTimeout(() => {
      resolve({
        success: status
      });
    }, 1000);
  });
}

/**
 * Just a helper random the result of the submit: success or failed
 *
 * @returns {boolean}
 */
const randomStatus = () => [true, false][Math.floor(Math.random() * 2)];

// Main body of the example
(async () => {
  const data = await getData();
  const names = data.map(data => data.name);
  console.log(names);
  const {success} = await submitData(names);
  if (success === true) {
    console.log("This is a successful form")
  } else {
    console.log("This is a failed form")
  }
})();

Заворачивать

Я надеюсь, что мой небольшой урок истории поможет вам уяснить эту важную концепцию. Приятного кодирования. Вы также можете прочитать это в моем личном блоге.