В конце 2015 года я услышал об этой паре ключевых слов, которые ворвались в мир JavaScript, чтобы спасти нас от promise chain hell, который, в свою очередь, должен был спасти нас от callback hell. Давайте посмотрим несколько примеров, чтобы понять, как мы дошли до async/await.

Допустим, мы работаем над нашим API и должны отвечать на запросы серией асинхронных операций:
— проверить валидность пользователя
— собрать данные из базы данных
— получить данные от внешнего сервиса
— изменить и записать данные обратно в базу данных

Также давайте предположим, что мы не имеем каких-либо знаний о промисах, потому что мы путешествуем назад во времени и используем функции обратного вызова, чтобы обработать запрос. Решение выглядело бы примерно так:

function handleRequestCallbacks(req, res) {
  var user = req.user
  isUserValid(user, function (err) {
    if (err) {
      res.error('An error ocurred!')
      return
    }
    getUserData(user, function (err, data) {
      if (err) {
        res.error('An error ocurred!')
        return
      }
      getRate('service', function (err, rate) {
        if (err) {
          res.error('An error ocurred!')
          return
        }
        const newData = updateData(data, rate)
        updateUserData(user, newData, function (err, savedData) {
          if (err) {
            res.error('An error ocurred!')
            return
          }
          res.send(savedData)
        })
      })
    })
  })
}

И это так называемый callback hell. Теперь вы знакомы с ним. Все его ненавидят, так как его трудно читать, отлаживать, изменять; он уходит все глубже и глубже во вложенности, обработка ошибок повторяется на каждому уровне и т.д.

Мы могли бы использовать знаменитую async библиотеку, чтобы немного очистить код. Код стал бы лучше, так как обработка ошибок, по крайней мере, была бы в одном месте:

function handleRequestAsync(req, res) {
  var user = req.user
  async.waterfall([
    async.apply(isUserValid, user),
    async.apply(async.parallel, {
      data: async.apply(getUserData, user),
      rate: async.apply(getRate, 'service')
    }),
    function (results, callback) {
      const newData = updateData(results.data, results.rate)
      updateUserData(user, newData, callback)
    }
  ], function (err, data) {
    if (err) {
      res.error('An error ocurred!')
      return
    }
    res.send(data)
  })
}

Позже мы узнали как использовать промисы и подумали, что мир больше не злится на нас; мы почувствовали, что нужно провести рефакторинг кода еще раз, ведь все больше и больше библиотек также движется в мир промисов.

function handleRequestPromises(req, res) {
  var user = req.user
  isUserValidAsync(user).then(function () {
    return Promise.all([
      getUserDataAsync(user),
      getRateAsync('service')
    ])
  }).then(function (results) {
    const newData = updateData(results[0], results[1])
    return updateUserDataAsync(user, newData)
  }).then(function (data) {
    res.send(data)
  }).catch(function () {
    res.error('An error ocurred!')
  })
}

Это гораздо лучше, чем раньше, гораздо короче и намного чище! Тем не менее возникло слишком много накладных расходов в виде множества then() вызовов, function () {...} блоков и необходимости добавлять несколько операторов return повсюду.

И наконец мы слышим о ES6, обо всех этих новых вещах, которые пришли в JavaScript: например стрелочные функции (и немного деструктуризации, чтобы было чуть веселее). Мы решаем дать нашему прекрасному коду еще один шанс.

function handleRequestArrows(req, res) {
  const { user } = req
  isUserValidAsync(user)
    .then(() => Promise.all([getUserDataAsync(user), getRateAsync('service')]))
    .then(([data, rate]) => updateUserDataAsync(user, updateData(data, rate)))
    .then(data => res.send(data))
    .catch(() => res.error('An error ocurred!'))
}

И вот оно! Этот обработчик запросов стал чистым, легкочитаемым. Мы понимаем что его легко изменить если нам нужно добавить, удалить или поменять местами что-то в потоке! Мы сформировали цепочку функций, которые одна за другой мутируют данные, которые мы собираем с помощью различных асинхронных операций. Мы не определяли промежуточные переменные для хранения этого состояния, а обработка ошибок находится в одном понятном месте. Теперь мы уверены, что определенно достигли JavaScript высот! Или еще нет?

И приходит async/await


Несколько месяцев спустя async/await выходит на сцену. Он собирался попасть в спецификацию ES7, затем идею отложили, но т.к. есть Babel, мы прыгнули в поезд. Мы узнали, что можем отметить функцию как асинхронную и что это ключевое слово позволит нам внутри функции «остановить» ее поток исполнения до тех пор, пока промис не решит, что наш код снова выглядит синхронным. Кроме того, функция async всегда будет возвращать промис, и мы можем использовать try/catch блоки для обработки ошибок.

Не слишком уверенные в пользе, мы даем нашему коду новый шанс и идем на окончательную реорганизацию.

async function asyncHandleRequest(req, res) {
  try {
    const { user } = req
    await isUserValidAsync(user)
    const [data, rate] = await Promise.all([getUserDataAsync(user), getRateAsync('service')])
    const savedData = await updateUserDataAsync(user, updateData(data, rate))
    res.send(savedData)
  } catch (err) {
    res.error('An error ocurred!')
  }
}

И теперь код снова выглядит как старый обычный императивный синхронный код. Жизнь продолжилась как обычно, но что-то глубоко в вашей голове говорит нам что что-то здесь не так…

Функциональная парадигма программирования


Хотя функциональное программирование было вокруг нас в течение более чем 40 лет, похоже что совсем недавно парадигма стала набирать обороты. И лишь в последнее время мы стали понимать преимущества функционального подхода.

Мы начинаем обучение некоторым из его принципов. Изучаем новые слова, такие как функторы, монады, моноиды — и вдруг наши dev-друзья начинают считать нас крутыми, потому что мы используем эти странные слова довольно часто!

Мы продолжаем свое плавание в море функциональной парадигмы программирования и начинаем видеть ее реальную ценность. Эти сторонники функционального программирования не были просто сумасшедшими. Они были, возможно, правы!

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

Но… постойте!


Мы видели все эти функциональные модели в прошлом. Мы помним, как мы использовали обещания и как соединяли функциональные преобразования один за другим без необходимости управлять состоянием или ветвить наш код или управлять ошибками в императивном стиле. Мы уже использовали promise-монаду в прошлом со всеми сопутствующими преимуществами, но в то время мы просто не знали это слово!

И мы вдруг понимаем, почему код на основе async/await смотрелся странно. Ведь мы писали обычный императивный код, как в 80-х годах; обрабатывали ошибки с try/catch, как в 90-х годах; управляли внутренним состоянием и переменными, делая асинхронные операции с помощью кода, который выглядит как синхронный, но который внезапно останавливается, а затем автоматически продолжает выполнение когда асинхронная операция будет завершена (когнитивный диссонанс?).

Последние мысли


Не поймите меня неправильно, async/await не является источником всего зла в мире. Я на самом деле научился любить его после нескольких месяцев использования. Если вы чувствуете себя комфортно, когда пишете императивный код, научиться использовать async/await для управления асинхронными операциями может быть хорошим ходом.

Но если вы любите промисы и хотите научиться применять все больше и больше функциональных принципов программирования, вы можете просто пропустить async/await, перестать думать императивно и перейти к новой-старой функциональной парадигме.

См. также


> Еще одно мнение о том, что async/await не такая уж хорошая вещь.
Поделиться с друзьями
-->

Комментарии (113)


  1. impwx
    24.01.2017 13:35
    +15

    Так и не понял аргументов автора. Судь async/await как раз в том, чтобы сделать асинхронный код максимально похожим на обычный, чтобы его было проще понимать и легче отлаживать.

    Мы не определяли промежуточные переменные для хранения этого состояния
    Иногда это бывает очень нужно, например для такого случая:

    const a = await getA();
    const b = await getB(a);
    const c = await getC(a, b);
    

    Аналогичный код с promise'ами выглядит очень гадко.


    1. VasilioRuzanni
      24.01.2017 14:25
      +6

      У статьи, конечно, несколько громкий заголовок — async/await — вовсе не шаг назад, особенно если программировать в императивном стиле (а он именно для этого и появился — не все пишут в функциональном и/или реактивном стиле). Аргумент автора лишь в том, что когда привыкаешь к функциональному подходу, он сам по себе уже кажется чище и элегантнее.

      У меня async/await был самой ожидаемой фичей ES-Next, пока не пришлось столкнуться с Observables :) Теперь async/await, в общем-то, не у дел, но это и не значит, что он плох.

      Ну и, к слову, несмотря на то, что код выглядит как синхронный, он именно что только «выглядит» так — асинхронность все равно надо держать в уме, и, поскольку await разворачивается в Promise, все равно нужно понимать, как именно он это делает.


    1. Fen1kz
      24.01.2017 15:03
      +1

      Promise.try(() => getA())
        .then(a => getB(a)
          .then(b => getC(a, b))
        );

      Ну… но я привы… ну ла… Не, стоп, но зато явная асинхронность и можно одновременно посылать несколько запросов + отменять их (с тем же bluebird).


      Ну то есть ваш пример вырожден. Добавьте ту же обработку ошибок, и у дела уже хуже.


      1. zekohina
        24.01.2017 15:45

        А такое?

        async function fetchAll(urls) {
            var results = [];
            for (let url of urls) results.push(await fetch(url));
            return results;
        }
        


        1. justboris
          24.01.2017 16:15
          +1

          Даже проще.


          function fetchAll(urls) {
            cosnt results = [];
            return urls.reduce(
              (promise, url) => promise.then(() => fetch(url)).then(user => results.push(user)), 
              Promise.resolve()
            ).then(() => results);
          }

          А еще вы в курсе, что ваш код отсылает запросы последовательно, один за одним?
          А если нужно параллельно?


          function fetchAll(urls) {
            return Promise.all(urls.map(url => fetch(url)));
          }

          а на промисах — раз и готово


          1. byme
            24.01.2017 16:44
            -1

            Паралельно и короче…

            async function fetchAll(urls) {
                return urls.asParalell().select(url => await fetch(url));
            }
            


            1. justboris
              24.01.2017 16:48
              +3

              А что такое asParallel()?


              В формулировке "проще", я имел в виду, что не нужно подключать Babel или колдовать с флагами в Node.js.


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


              1. raveclassic
                24.01.2017 16:50
                +6

                Если мне не изменяет память, конструкция asparallel/select — это rxjs. Круто. Притащили rx.


                1. byme
                  24.01.2017 17:17

                  Вот мы и подхошли к мысли, что все подходы хороши, но при одном условии, если их использовать в нужных местах=)


                  1. raveclassic
                    24.01.2017 17:59

                    Круто. Притащили rx.
                    Скрытый текст


                    1. byme
                      24.01.2017 18:01

                      А что плохого в том что бы использовать несколько подходов? Религия не позволяет?


                      1. raveclassic
                        24.01.2017 18:07

                        Если вы сейчас про .Net, то странно его упоминать в контексте обсуждения «async/await или Promise в JS».
                        Если вы все же про JS, то зачем «несколько подходов» для стандартного способа распараллеливания и сбора результатов Promise.all? Уже даже await* задеприкейтили в пользу последнего.


              1. byme
                24.01.2017 17:13

                По сути map который возвращет таски, которые инкапсулируют елементы массива и позволяют их обрабатывать парелельно. Я его так назвал по аналогии с миром .Net где async/await єсть уже относительно давно и метод AsParallel стал дополнением стандартной библиотеки. Имя может быть другим не в этом суть. Суть том что вместе с раширением возможностей языка будут расширены и возможности библиотек, не стоит об этом забывать.


                1. justboris
                  24.01.2017 18:38

                  На ваш код Babel ругается:
                  https://babeljs.io/repl/#?babili=false&evaluate=true&lineWrap=false&presets=es2015%2Ces2016%2Ces2017%2Cstage-3&code=async%20function%20fetchAll(urls)%20%7B%0A%20%20%20%20return%20urls.asParalell().select(url%20%3D%3E%20await%20fetch(url))%3B%0A%7D


                  И он прав, потому что async написан у корневой функции, а await внутри другой, которая идет аргументом в map. Async так не работает.


          1. zekohina
            24.01.2017 16:52
            +3

            Так ведь async/await и не отменяет промисы. Там где нужна параллельность, действительно можно использовать Promise.all(), но в моем примере нужна была именно последовательность.


          1. TheShock
            24.01.2017 21:13

            function fetchAll(urls) {
              cosnt results = [];
              return urls.reduce(
                (promise, url) => promise.then(() => fetch(url)).then(user => results.push(user)), 
                Promise.resolve()
              ).then(() => results);
            }
            


            Почему вы считаете, что такой код проще такого? Зачем reduce, который усложняет понимание кода?

            function fetchAll(urls) {
              const results = [];
              let promise = Promise.resolve();
              
              for (let url of urls) {
                promise = promise
                  .then(() => fetch(url))
                  .then(user => results.push(user))
              }
              
              return promise.then(() => results);
            }


            1. justboris
              24.01.2017 21:46

              Кому-то больше нравится писать код с for-циклами, а кто-то больше любит методы массива.


              Тут уже личный выбор каждого. Я предпочитаю, не бегать по массиву через for, а использовать filter/map/reduce, но это мое личное предпочтение.


            1. kana-desu
              25.01.2017 00:35

              Редьюс будет все же проще, я вам как хаскелист говорю


              1. TheShock
                25.01.2017 01:51

                В Хаскеле? Возможно)


          1. DistortNeo
            24.01.2017 22:26
            +3

            async/await — просто синтаксичский сахар для Promise, не вижу причин спорить


          1. vintage
            24.01.2017 22:51
            -2

            Вариант с атомами:


            fetchAllParallel( urls ) {
                return urls.map( url => $mol_resource.item( url ).text() )
            }
            fetchAllSerial( urls ) {
                return urls.map( url => $mol_resource.item( url ).text().valueOf() )
            }


            1. justboris
              25.01.2017 00:27

              Вроде же изначально договорились никакие библиотеки не использовать.


              Так-то я могу тоже взять гипотетическую библиотеку url-fetcher, которая делает то, что мне нужно, надо лишь ее вызвать.


              fetchUrls(urls);


              1. vintage
                25.01.2017 00:43
                -2

                Вы видимо тайком договаривались. :-)


                В том-то и дело, то атомы не "делают то, что вам нужно". Они лишь позволяют синхронный код исполнять асинхронно:


                printTitlesParallel( urls ) {
                    for( let text of this.fetchAllPrallel( urls ) ) {
                        this.log( this.parseHTML( text ).title )
                    }
                }
                printTitlesSerial( urls ) {
                    for( let text of this.fetchAllSerial( urls ) ) {
                        this.log( this.parseHTML( text ).title )
                    }
                }


                1. justboris
                  25.01.2017 00:46
                  +2

                  Не спорю, что с атомами, как и с rx, async.js и другими библиотеками код будет короче чем без них.


                  Но вообще это комментарии к статье о том, нужен ли async/await в стандарте языка. Библиотеки здесь не при чем.


                  1. vintage
                    25.01.2017 09:05

                    А при чём тут библиотеки? Я говорю о концепции. И концепция писать синхронный, но не блокирующий код — нужна.


          1. DistortNeo
            24.01.2017 23:53

            Даже проще.

            Но есть нюанс: вызов urls.reduce приведёт к созданию огромного объекта
            Promise.resolve().then(...).then(...).then(...).then(...).then(...).then(...)....,
            тогда как код с async/await будет генерить промисы по мере необходимости.


            В реальной ситуации это, конечно, не играет никакого значения — вряд ли вы будете по 100000 промисов отрабатывать за раз.


            1. justboris
              25.01.2017 00:35

              А чем именно плоха большая цепочка then?


              Зато можно быть уверенным, что пройдутся все урлы, а не потеряется что-то в процессе, если кто-то снаружи смутирует массив urls. Вот пример. Очень невероятный пример, но все же.


              1. DistortNeo
                25.01.2017 01:55

                Точно так же можно смутировать и элементы массива urls, если они представляют собой объекты, не строчки. Ещё более невероятная ситация, но тоже возможна.


                1. justboris
                  25.01.2017 01:56

                  Хорошо, а что с ответом на вопрос:


                  А чем именно плоха большая цепочка then?


                  1. TheShock
                    25.01.2017 02:21
                    +1

                    Мне вообще этот подход с промисами кажется странным и необходимость в них (а уж тем более такая расширенная) попахивает плохой архитектурой. У вас есть данные. Вы заносите их в модель (можно даже паралельно) и когда данных в модели приложения достаточно — выполняете действие.

                    Т.Е. у нас есть модуль обновления модели:

                    for (var model of neededModels) {
                      model.load();
                    }
                    


                    А также есть модуль (никак не связанный с загрузкой моделей), который отвечает за выполнение какого-либо действия:
                    onModelLoad(models) {
                      if (this.allModelsReady(models)) {
                        this.doMyAction();
                      }
                    }
                    


                    Мы к этому подходу пришли задолго до бума промисов и опробовали его на огромной игре — когда отказались от прямой подписки на события («загрузи и сделай это»), то сложность приложения значительно снизилась, а «колбек-хел» забылся как страшный сон.

                    Потому не могу понять, почему с ними до сих пор носятся так сильно.

                    Чем плоха большая цепочка then? Да потому что что угодно может произойти, пока она выполняется. А еще нельзя показать промежуточный уже загруженный результат. А ещё нельзя не выполнить колбек (если пользователь ушел с этого экрана). И вообще ад колбеков никуда не девается.


                  1. DistortNeo
                    25.01.2017 02:22
                    +1

                    Просто создаётся слишком тяжёлый объект, но я это проблемой не считаю.


                    Вот более сложный пример для перевода в промисы:


                    function fetchUntilAvailable(url) {
                      while (true) {
                        let result = await fetch(url);
                        if (isGood(result))
                          return result;
                        else
                          await delay(timeOut);
                      }
                    }


                    1. Fen1kz
                      26.01.2017 15:04
                      +1

                      const fetchUntilAvailable = (url) => 
                        fetch(url)
                          .then(result => isGood(result) 
                            ? result
                            : new Promise(resolve => setTimeout(resolve, timeOut))
                                .then(() => fetchUntilAvailable(uri)))

                      Вообще плохо что я повелся и написал это так, ваша функция грязная и пахнет =(


                      Из контекста приходят: timeOut, delay (зачем брать из контекста delay и из него же timeOut?), isGood. Тестировать такую функцию почти невозможно =/


                      1. raveclassic
                        26.01.2017 15:05
                        +6

                        Вы серьезно считаете, что это читабельней?


                        1. TheShock
                          26.01.2017 18:24
                          +1

                          Вы ведь знаете что я хочу сказать, да?
                          Плевать на нечитаемость! Зато другие хипстеры на афтерпати будут восхищены!


                          1. Fen1kz
                            27.01.2017 02:35

                            Да успокойся ты, глупый хипстерофоб. Я же приписал в конце — что функция убогая и не тестируемая сама по себе, так что я ошибся когда вообще попытался написать ее "красиво"


                            1. TheShock
                              27.01.2017 02:45
                              +1

                              Я глупый? Ну-ну. Прекрасная попытка оскорбления! Сразу показывает ваш уровень и силу аргументов. Теперь то очевидно, что вы были правы и промисы значительно лучше async-await, спасибо, что открыли мне глаза.

                              А если серьезно, то раз так сложно в таком стиле написать хорошо, то может не выпендриваться и просто писать хороший код в классическом стиле, а не стараться угнаться за сиюминутной модой?


                        1. Fen1kz
                          27.01.2017 02:38
                          +1

                          Давайте так, я пытался и у меня не получилось, ок?


                      1. justboris
                        26.01.2017 15:51

                        function fetchUntilAvailable(url, retryTimeout) {
                           return fetch(url).then(result => {
                              if(!isGood(result)) {
                                   return delay(retryTimeout).then(() => fetchUntilAvailable(url, retryTimeout));
                              }
                              return result;
                           });
                        }

                        Согласен, время timeout можно передать в аргументах. А в утилитарной функции delay не вижу ничего плохого. Удобнее, чем каждый раз new Promise разворачивать


                      1. TheShock
                        26.01.2017 18:21

                        (зачем брать из контекста delay и из него же timeOut?)

                        delay — библиотечная функция, timeOut — константа в проекте. А как иначе?
                        Ваш код стремится к нечитаемости


                    1. reforms
                      26.01.2017 15:51

                      Ваш пример еще можно уменьшить

                      function fetchUntilAvailable(url) {
                        while (true) {
                          let result = await fetch(url);
                          if (isGood(result))
                            return result;
                          await delay(timeOut);
                        }
                      }
                      
                      //А насчет промисов тоже решение имеется:
                      function fetchUntilAvailable(url) {
                        return fetch(url).then(function(result) {
                          return isGood(result) && result
                            || delay(timeOut).then(() => fetchUntilAvailable(uri)));
                        });
                      }
                      //при условии, что delay возвращает Promise
                      

                      Хотя Ваш вариант (await) мне нравится куда больше.


        1. abby
          24.01.2017 16:21

          Это к чему? Тут как раз, скорее всего, async-await не нужен, потому что, скорее всего, нет смысла обрабатывать URL'ы по очереди, было бы лучше «одновременно».

          fucntion fetchAll(urls) {
            return Promise.all(urls.map(fetchAsync));
          }
          


          1. zekohina
            24.01.2017 16:25

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


            1. abby
              24.01.2017 16:30

              Когда на тысячи я использую хак (а может так и надо?) в виде пула, но я полностью за Ваш вариант с async-await по сравнению с примером выше с reduce, если нужно именно последовательно.


            1. justboris
              24.01.2017 16:32

              Ограничивать число одновременных запросов можно на уровне модуля http.
              Зачем засорять логику приложения такими подробностями?


        1. Fen1kz
          24.01.2017 16:34

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


          1. zekohina
            24.01.2017 16:43

            Где ошибся?


            1. Fen1kz
              24.01.2017 16:50
              +1

              Ну, я написал "можно одновременно посылать несколько запросов"


              А у вас последовательные. Это не одновременно.


    1. TheShock
      24.01.2017 15:28
      +8

      Так и не понял аргументов автора.

      "Async/await плохо, потому что это императивно, а значит не модно, а нас ведь считают крутыми, потому что мы пишем на ФП."


      Без шуток. Это аргументация профессиональных программистов. И они правда так думают.


  1. potan
    24.01.2017 13:46

    У нас, так исторически сложилось, что тесты написаны асинхронно. Правда на Scala, но сути это не меняет.
    При этом используются целых три стиля — flatMap (на сколько я понимаю, это в js называется «через callback»), с помощью for (в js аналога, по моему, нет, похоже на do в Haskell), и async/await.
    Для меня с flatMap единственное неудобство — большой уровень вложенности синтаксических конструкций (соответственно проблема, как поступать с отступами). for выглядит чище, я последнее время склоняюсь к нему. Стиль async/await имеет много фанатов, но мне от показался малопонятным (я давно программирую на функциональных языках и более императивный код мне читать сложнее). Кроме того, в этом коде очень легко где-нибудь забыть написать await на какую-нибудь фьючу, результат которой игнорируется (в нашем случае это обычно было удаление созданных для теста сущностей), и получить race condition.
    В общем, async/await мне не понравился.


  1. Jabher
    24.01.2017 15:03
    +5

    1. промисы не решали в понятном виде большое количество проблем. Например — использование while или switch-case внутри логики (а особенно все вместе).
    2. правильно воспринимать их не как сахар, а как специфическую инструкцию, которая делает 2 отдельных вещи: уступает поток исполнения и ожидает выполнения результата. Мы можем использовать ее и для того, и для другого.
    3. async/await действительно является шагом назад под давлением внешних факторов, но не потому что она не функциональная, а потому что реализует подмножество функционала функций-генераторов, и не имеет особого смысла (кроме возможностей для подсветки и простоты самой по себе). Я бы предпочел видеть синтаксис вроде Promise.generator(function() {}), он бы дал куда больше гибкости и возможностей для тестирования — как минимум за счет инструкции yield.
    4. то, что вы любите функциональное программирование, не значит, что его любят все. JS пытается стать универсальным языком, а не вторым OCaml.


    1. staticlab
      24.01.2017 16:25

      синтаксис вроде Promise.generator(function() {})

      Можно ведь разработать proposal.


      1. Jabher
        24.01.2017 17:03

        А смысл? сделали же уже async/await. Много разных API это не хорошо.
        А асинки уже вошли в es2017, если не ошибаюсь.


  1. TheShock
    24.01.2017 15:15
    +5

    Изучаем новые слова, такие как функторы, монады, моноиды и вдруг наши dev-друзья начинают считать нас крутыми, потому что мы говорим эти странные слова довольно часто!

    Скорее не крутыми, а смешными.

    Ведь мы писали обычный императивный код, как в 80-х годах, обрабатывали ошибки с try/catch, как в 90-х годах

    О нет! Если я напишу более читаемый и поддерживаемый код, то современные хипстеры меня засмеют, ибо это уже не модно и вообще, так писали в 90-х, какой позор!

    Статью писал сектант. «Не юзайте асинк-евейт, т.к. это богомерзкая императивщина, а свидетели ФП не используют ничего из мира императивщины, даже если это значительно удешевит поддержку кода». Фу.

    И снова не крутые, а смешные, как и обычно.

    Кстати, ваш священный Редакс тоже написан в императивном стиле. Видимо, чтобы продавать веру простому люду не обязательно самому в это верить:
      function subscribe(listener) {
        if (typeof listener !== 'function') {
          throw new Error('Expected listener to be a function.')
        }
    
        let isSubscribed = true // мутабельность
    
        ensureCanMutateNextListeners()  // нет чистоты
        nextListeners.push(listener)  // мутабельность, нет чистоты
    
        return function unsubscribe() {
          if (!isSubscribed) { // нет чистоты
            return
          }
    
          isSubscribed = false // мутабельность, нет чистоты
    
          ensureCanMutateNextListeners() // нет чистоты
          const index = nextListeners.indexOf(listener) // нет чистоты
          nextListeners.splice(index, 1) // мутабельность, нет чистоты
        }
      }
    


    1. raveclassic
      24.01.2017 15:50
      +2

      О нет! Если я напишу более читаемый и поддерживаемый код, то современные хипстеры меня засмеют, ибо это уже не модно и вообще, так писали в 90-х, какой позор!
      Хотите сказать, на ФП не писали в 90х? Или 80х? Ну конечно, ФП же придумали хипстеры, которые вам спать по ночам не дают ;)


      1. TheShock
        24.01.2017 16:04
        +1

        О чем вы? Я ведь процитировал предложение из топика по поводу которого была моя ирония. Или вы увидели только то, что хотели?


        Процитирую лично для вас, Этот текст написан в статье:


        Ведь мы писали обычный императивный код, как в 80-х годах, обрабатывали ошибки с try/catch, как в 90-х годах

        Так что ваш вопрос должен быть адресован к автору топика


        1. raveclassic
          24.01.2017 16:11
          +4

          Нет, просто рвение, с которым вы постоянно жалуетесь на вездесущих хипстеров, вводит в недоумение, если честно. С мыслью-то я согласен, неудачно уловил контекст.


          1. TheShock
            24.01.2017 16:23
            +4

            Я могу объяснить. Вот человек не определился, заходит в такую статью и начинает ей верить, т.к. не имеет достаточно контекста.

            Если не высмеивать подобные творения, то получим еще одного программиста, который судит категориями «это круто» / «это модно» / «надо отказываться от ооп в пользу фп» а то и начинает тоже писать такие статьи. Если бы наша среда была крайне конкурентной, то может оно было бы и хорошо, но, к сожалению, сейчас очень трудно найти себе в команду хороших программистов.


            1. raveclassic
              24.01.2017 16:39
              +2

              Ну вы же понимаете, что хороший программист не получится из чужих советов. Давайте вспомним себя, когда мы все с остервенением педалили собственные фреймворки, реализации классов на js, библиотеки для дат, да чего там только не было, и так педалили, что дальше носа ничего не видели. Ну как? :)
              Вот и тут, хорошим программист станет тогда, когда сам наберется опыта, лично наступив на каждую граблю. Я понимаю вашу усталось от всего этого фанбойства, но с этим ничего не поделать.

              сейчас очень трудно найти себе в команду хороших программистов.
              Горькая правда


              1. TheShock
                24.01.2017 17:08
                +5

                Ну на самом деле вы зря недооцениваете влияние подобных статей и комментариев на начинающих программистов. Оно огромно.

                Да, педалили, набирались опыта, нас критиковали, на какую-то критику обижались, какая-то была слишком аргументированной, чтобы ее не слышать.

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

                Вот спорит сторонник фреймворка А и фреймворка Б, каждый говорит, что его лучше, а другой хуже. А ты читаешь и узнаешь, вот, оказывается, если смотреть на фреймворк А под таким углом, то он и ничего вообще-то, а вот эта фича в фреймворке Б оказывается так используется.


      1. TheShock
        24.01.2017 16:18
        +8

        Судя по оповещению, вы еще спрашивали:

        А что смешного-то?

        Да много чего.
        — Смешно то, что «императивный подход — это девяностые, а функциональный — это десятые».
        — А еще, что это считается важным аргументом в технической статье.
        — Что думают, что пишут на JS в функциональном стиле, а в коде не имеют ни чистоты ни декларативности
        — Что используют алгоритм квадратичной сложности, где более читаемый и очевидный алгоритм линейной сложности просто чтобы код казался функциональным.
        — Потому что мерилом качества кода выступает не «мы поддерживаем огромный продукт уже 3 года, но все-равно с легкостью и минимум багов вносим в него функционал», а «наши dev-друзья начинают считать нас крутыми, потому что мы говорим эти странные слова довольно часто!»
        — Смешна не функциональная парадигма, она очень крута в определенных областях, смешны люди, которые ее продвигают подобными статьями.


        1. raveclassic
          24.01.2017 16:48

          Да я что-то задумался, и подумал, что вы это про термины теории категорий. Ну и удалил, когда перечитал внимательней.


  1. kernel72
    24.01.2017 15:15
    +1

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


    1. Source
      24.01.2017 22:38
      +1

      Мне кажется, имелось в виду, что явное в данном случае лучше неявного. И если писать асинхронный код, делая вид, что он синхронный, то рано или поздно начнётся танец по граблям.


      1. DistortNeo
        24.01.2017 23:43
        +1

        Программисты C# уже давно пишут асинхронный код с использованием async/await и не испытывают никаких проблем с граблями. Более того, код с использованием async/await получается более производительный, чем основанный на использовании Task.ContinueWith (аналог JS Promise.then).


        1. Source
          25.01.2017 01:15

          Программисты C# уже давно пишут асинхронный код с использованием async/await и не испытывают никаких проблем с граблями.

          С чего Вы так решили? Ещё как испытывают и часть из этих проблем даже официально признана и решается последующими релизами C# 6, C# 7, etc.
          В принципе, тут невозможно сделать недырявую абстракцию, т.к. код по факту асинхронный и попытки это замаскировать, прикинувшись, что он как бы такой же как синхронный, ни к чему хорошему не ведут. Просто чем дальше, тем более изощрёнее будут грабли.


          1. DistortNeo
            25.01.2017 02:23
            +2

            С чего Вы так решили? Ещё как испытывают и часть из этих проблем даже официально признана и решается последующими релизами C# 6, C# 7, etc.

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


            Что касается C# 6.0: просто ввели возможность написания await в catch и finally блоках, что ещё больше сделало асинхронный код похожим на синхронный. В C# 7.0 существенных изменений больше нет.


            1. Source
              25.01.2017 11:49

              В C# 7 — "Generalized async return types", в каком-нибудь C# 8 ещё с out-параметрами накостылят или ещё что-нибудь. В принципе, о том и речь, что асинхронный код, похожий на синхронный, будет всегда плохим решением, потому что по факту он несинхронный. И аргумент, что те, кто хорошо понимает что происходит за сценой, не испытывают проблем — не работает. Потому что вся эта декорация делается не для них, а для тех, кто ещё толком не понимает… типа "не задумывайся, что код больше не синхронный, просто добавь воды async/await".


              1. staticlab
                25.01.2017 13:13

                То есть опытным программистам не нужен сахар?


                1. Source
                  25.01.2017 22:35

                  Сахар сахару рознь… Хороший сахар делает явное ещё более явным, плохой — явное делает неявным.


              1. DistortNeo
                25.01.2017 19:58

                Generalized async return types в C# я уже пощупал — фигня это, правда, тем более, что они все равно на Task завязаны. Прироста в производительности по сравнению с обычным Task вообще нет — основной оверхед там от state machine идёт. Теоретически, польза от этого есть при полном переписывании штатного TPL на свой, с блекджеком и шлюхами.


                1. Source
                  25.01.2017 22:39

                  Так я и не имел в виду, что будут какие-то действительно полезные или масштабные доработки. Это скорее затыкание дыр в абстракции "асинхронный код, похожий на синхронный", которая дырява by design.


                  1. vintage
                    25.01.2017 22:44

                    Наоборот. Асинхронный код — костыль для многозадачности в рамках одного потока. В нормальных языках есть потоки и/или сопрограммы. И те и другие — синхронны.


                    1. DistortNeo
                      25.01.2017 23:37
                      +1

                      Так и есть. В чём принципиальная разница между сотней асинхронных задач и сотней потоков, ожидающих выполнения? Да ни в чём. Просто механизмы синхронизации на уровне операционной системы дорогие, вот и приходится выкручиваться с помощью кооперативной многозадачности в юзерспейсе.


                      1. lega
                        26.01.2017 02:06

                        Просто механизмы синхронизации на уровне операционной системы дорогие, вот и приходится выкручиваться с помощью кооперативной многозадачности в юзерспейсе.
                        Речь не про уровень ОС, а про корутины на уровне программы которая написана синхронным кодом. Дак вот по сравнению с ним, асинхронный код — костыль.


                      1. vintage
                        26.01.2017 06:12

                        В том, что потоков будет не сотня, а на порядок меньше. Сопрограмм (которые могут жить и на одном потоке) тоже будет на порядок меньше. И только асинхронное программирование требует разбиения бизнес задачи на множество мелких асинхронно исполняющихся технических подзадач вида "обработать ответ от такого-то апи и вызвать следующее".


                    1. Cryvage
                      26.01.2017 18:02
                      +1

                      В этом случае попытки замаскировать асинхронный код под синхронный проблему не решают. Если асинхронный код — это костыль, то async/await это костыль для другого костыля. Настоящий kostyl-driven design во всей своей красе.


                      1. vintage
                        26.01.2017 21:18

                        Конечно костыль, поэтому мы с помощью реактивности абстрагируемся от (а)синхронности.


                  1. Cryvage
                    26.01.2017 17:43
                    +1

                    Майкрософт вообще частенько грешит подобным подходом. Ту же самую ошибку они допустили, когда выкатывали Web Forms, в котором пытались сделать разработку web приложений похожей на Windows Forms. Естественно «не взлетело». Вместо этого «взлетели» те походы, которые не скрывали особенности web разработки, а наоборот подчёркивали их и учитывали в своей архитектуре. За счёт этого им удалось сделать web разработку более простой и удобной.


                    1. Source
                      27.01.2017 00:57

                      O, да! Web Forms — это был гораздо более крутой изврат… Я помню фееричный анонс UpdatePanel (для тех, кто не в теме, это такой компонент на форму внутри которого автоматически AJAX типа работает) от MS-евангелиста, вот это был реальный треш и угар.


      1. aikixd
        25.01.2017 01:05

        Асинки не параллельные. Код все равно последовательный, просто код не виснет когда ожидает ответа.


  1. abby
    24.01.2017 15:18
    +1

    Я совсем не эксперт в функциональном программировании и в async-await в JS, но, на мой взгляд, пример в статье совсем не отражает её суть.
    В частности, практически каждый метод имеет побочные эффекты, любой метод может изменить внутреннее состояние аргумента, и, в общем-то, часто именно эти явления и ожидаются. То есть, идеологически полный провал.
    Кстати, `isUserValidAsync` было бы лучше назвать assertValidUserAsync или использовать по-другому.

    С другой стороны, в случае использования async-await, на мой взгляд, существенно улучшается читабельность кода от чего напрямую зависит его качество и стоимость поддержки. Хотелось бы заметить, что читабельность улучшается за счет меньшего количества всяких скобочек, запятых, вызываемых методов и вообще меньше кода.

    При этом никто не запрещает использовать элементы функционального подхода, только без лишних ограничений на способ выражения желаемого. К тому же, наверняка, JS-движок следующей версии сможет существенно оптимальнее представить внутри себя async-await конструкции, чем свалку promise'ов, а программист все так же может продолжать использовать более менее простую модель с выворачиванием этого всего в promise'ы.


  1. reforms
    24.01.2017 17:21

    К сожалению ничего не могу сказать по поводу async/await, но вот при использовании Promise в продакшене столкнулись с такой проблемой, что стали появляться синхронно-асинхронные функции:

    function doSomeWork(request) {
        // код...
        // где в середине
        if (request == null) {
            return;
        }
        // где-то в конце, doPost возвращает Promise.
        return request.doPost(/**...*/);
    }
    

    из-за чего лезут нехорошие баги. Хотя это не проблема Promise, а их некорректное использование.

    На счет статьи показалось, что автор поведал нам хронологическую ленту развития JS


    1. JSmitty
      24.01.2017 17:37
      +1

      Попахивающий код. Сигнатура функции вообще произвольная (тип возвращаемого не определен)? Что мешает всегда возвращать промис (Promise.reject() например)?


      1. zekohina
        24.01.2017 17:45

        Код вполне типичный. Просто во всех местах где будет юзаться эта функция делается что-то типа такого:

        const result = doSomeWork(request);
        if (!result) return;
        // Дальше нормальное выполнение
        


        1. reforms
          24.01.2017 17:47
          +1

          Да такие ветки имеются, и даже такие Promise.resolve(doSomeWork(request))


    1. atd
      24.01.2017 20:58
      +1

      Как раз в таком примере as/aw вас спасёт: любая async-функция всегда вернёт промис


  1. reforms
    24.01.2017 17:42

    Promise.resolve вы наверное имели в виду? Да ничего и про сигнатуру Вы правы, я просто делюсь что имеем по факту.


  1. vintage
    24.01.2017 22:18
    -3

    Решение выглядело бы примерно так:

    Не так, вы забыли позаворачивать все хендлеры в try-catch-и, иначе любая ошибка убьёт процесс.


    1. Maiami
      25.01.2017 04:24

      Их не надо заворачивать в try-catch, потому что верхний try-catch поймает все исключения ниже


      1. vintage
        25.01.2017 09:14
        +1

        Речь о коде на колбяках. Один try-catch ничего не поймает.


  1. Lalartu
    25.01.2017 00:58
    +1

    Ну на самом деле async/await скорее можно назвать синтаксическим сахаром для CPS, так что обвинения в императивности, как минимум, выглядят голословными. Как инструмент async/await довольно удобен, проблема опять в том, как его использовать.


  1. Mithgol
    25.01.2017 02:14
    +3

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

    Мне кажется, что спохватываться надо было гораздо раньше: по сравнению с возможностями библиотеки Async даже промисы выглядят шагом назад.

    Дело в том, что у промисов есть метод «Promise.all», тогда как у библиотеки Async есть не только ананалогичный ему метод «async.parallel» (а также другой метод «async.waterfall», аналогичный цепному вызову промисов через их «.then»), но и ещё почти шестьдесят других удобных методов. Оговорюсь, что из них лично я использую никак не больше десятка, но, во-первых, даже этот десяток не горю желанием сочинять самостоятельно ради перехода на промисы, а во-вторых, меня радует сознание того, что за пределы этого десятка я всегда смогу (при малейшей необходимости) выйти почти мгновенно, потому что там ждут меня ещё полсотни готовых методов, которые опять же не придётся сочинять самостоятельно.

    То есть переход на промисы выглядит как переход на новый (и ещё не обросший возможностями) фреймворк, ради которого отбрасывается история многих лет эволюции библиотеки Async (начиная от первого коммита 13 мая 2010 года) и большинство обретённых ею возможностей, так что теперь промисовая публика будет переизобретать велосипеды (причём, скорее всего, опять же на протяжении нескольких лет кряду) при столкновении с наималейшими проблемами действительности.

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

    Поэтому лучшее, что можно сделать с промисом — это сунуть его в «async.asyncify» и дальше коллбекнуть по старинке.

    Your callback hell is my home.


    1. Maiami
      25.01.2017 04:30
      +2

      Всё так и есть, не поспорить
      Но async-await всё равно удобные. Удачно удалось сократить один модуль на промисах, переписав его на async-await, на 40%, и добавлять в него новый функционал стало проще и приятнее ^__^


    1. zapolnoch
      25.01.2017 12:26
      +1

      По этой логике можно вообще не развивать язык и пусть всю работу делают фреймворки. Зачем вам перебирающие методы массива (map, filter и т.д.), если у вас есть цикл for? Если нужно что-то сложнее, то используйте фреймворки, типа lodash. Собственно, именно из-за такой логики мы и находимся в ситуации, когда до 2015 года (больше чем за 15 лет своего существования), JS-стандарт обновлялся только два раза. Именно поэтому JS стал восприниматься как ассемблер на котором невозможно что-то сложное написать, а шутка про день, когда не было создано ни одного нового JS-фрейморка уже набила оскомину.
      То что «фреймворк» делают частью языка меняет абсолютно все. Как минимум, можно быть уверенным что выполнение подобных задач будет оптимизировано на уровне интерпретатора и это положительно скажется на производительности.


  1. justfly1984
    25.01.2017 11:07
    +3

    По моему всё гораздо проще: Удобно использовать async/await => используем async/await. Удобно Promise писать, пишем Promise. Если код дорого переписывать на FP, оставляем императив. Это более чем Advanced javascript. Новички всё равно будут отстреливать себе ноги и с тем и с другим и с третьим. Если ты профи с FP, это не означает что ты никогда не будешь писать/обновлять OOP. Если ты катаешься по конференциям и твой твиттер полон фэнов хипстеров, это не означает что ты всегда прав. В конце концов даже Дуглас Крокфорд был неправ по поводу;

    PS: и для Promise и для async/await пока без полифилов можно писать только на Node.js и прототипы в хроме канари. Сафари не поддерживает без полифилов fetch API, Edge… нет на Маках, Хрома нет на IOS Но господи как я рад что сейчас не 2009 год и это самые большие проблемы хайпа.
    Babel 6, Webpack 2, React 15, ES6+ сделали жизнь на много проще.


    1. zorro1211
      28.01.2017 06:10

      Ура, нашелся оптимистично настроенный человек наконец-то =) Спасибо, подняли настроение.


  1. LMnet
    26.01.2017 06:27
    +1

    В Scala (и scala.js) существуют и промисы (только используется другой нейминг, вместо Promise они называются Future) и async/await. И все предпочитают использовать именно промисы (фьючи). Причина в том, что async/await создает ложную иллюзию синхронности происходящего. Это может привести к тому, что синхронная и асинхронная логика будут смешаны вместе и в какой-то момент await потеряется перед асинхронной функцией. Или наоборот, await "на всякий случай" будет приписываться везде, даже если функция не асинхронная. При использовании промисов синхронные и асинхронные потоки явно отделены друг от друга. Глазами сразу видно, где тут у нас асинхронное выполнение, а где синхронное. И это потенциально более безопасно и не приведет к ошибкам, которые я выше описал.
    Чтобы асинхронный код не превращался в кашу в Scala используется for-comprehension. И такой асинхронный код выглядит почти как синхронный, но при этом явно видно, где тут синхронные части, а где асинхронные.
    В Scala/Scala.js коде пример выше выглядел бы примерно следующим образом:


    def handleRequestArrows(user: User, sender: ResultSender): Future[SendingResult] {
        val res = for {
            _ <- isUserValidAsync(user)
            (data, rate) <- Future.sequence(Seq(getUserDataAsync(user), getRateAsync("service")))
            savedData <- updateUserDataAsync(user, updateData(data, rate))
            sendingResult <- sender.send(savedData)
        } yield sendingResult
        res.andThen { // обычно это делают на уровне выше, но я добавил сюда, чтобы не отступать от примера
            case Failure(e) => logger.log("An error ocurred", e)
        }
    }

    Код вполне наглядный. Видно, что за чем идет. Если какой-то шаг вернет ошибку, то выполнение всего блока for-comprehension прервется.


    1. vintage
      26.01.2017 07:46

      И чем стрелочка и for принципиально отличаются от await и async соответственно? :-)


      1. staticlab
        26.01.2017 07:56

        Думаю, подразумевается, что в блоке for-yield не нужно await-ить каждую асинхронную операцию (соответственно нет риска забыть где-нибудь await), вдобавок при этом явно выделен асинхронный блок.


        1. vintage
          26.01.2017 08:28

          Что такое стрелочка как не await?


          1. staticlab
            26.01.2017 09:57

            В принципе да. Посмотрел, что стрелочка — это синтаксис генератора (не обратил внимания — сказался опыт R). То есть на JS действительно можно сделать то же самое, если for-yield заменить на анонимную асинхронную функцию, а стрелочки на await. В результате получим, что


            такой асинхронный код выглядит почти как синхронный, но при этом явно видно, где тут синхронные части, а где асинхронные.


      1. LMnet
        26.01.2017 10:30

        Все асинхронные функции явно вынесены внутрь for блока. В случае с async/await синхронные и асинхронные функции перемешаны на одном уровне.


        1. staticlab
          26.01.2017 10:47
          +1

          Вот обратно портировал код со Скалы на JS:


          function handleRequestArrows(user, sender) {
              const res = async () => {
                  await isUserValidAsync(user);
                  const [data, rate] = await Promise.all([getUserDataAsync(user), getRateAsync("service")]);
                  const savedData = await updateUserDataAsync(user, updateData(data, rate));
                  const sendingResult = await sender.send(savedData);
                  return sendingResult;
              };
              res.catch(e => {
                console.log("An error ocurred", e);
              });
              return res;
          }

          Все асинхронные функции явно вынесены внутрь блока async () => { ... }. Найдите 10 отличий, как говорится.


          1. LMnet
            26.01.2017 10:53
            -1

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


            1. staticlab
              26.01.2017 10:59

              В Скале можно выполнить синхронный код внутри for-yield?


              1. LMnet
                26.01.2017 11:04

                Можно. Примерно так:


                for {
                    resAsync1 <- someAsyncFun1(args)
                    resSync = someSyncFun(resAsync1)
                    resAsync2 <- someAsyncFun2(resSync)
                } yield resAsync2

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


                1. staticlab
                  26.01.2017 11:11

                  Я так понимаю, что в Скале компиляция чего-то вроде


                      resAsync1 = someAsyncFun1(args)
                      resSync = someSyncFun(resAsync1)
                      resAsync2 = someAsyncFun2(resSync)

                  приведёт к ошибке компиляции из-за строгой типизации. В таком случае аналогично помочь может TypeScript.


                  1. LMnet
                    26.01.2017 12:18

                    Если ты засунешь эти три строки в for-comprehension — то да, будет ошибка компиляции.
                    Насчет TypeScript — в нем нету аналога for-comprehension. То есть там будет такая же ситауция, как и в js.


                    1. raveclassic
                      26.01.2017 12:24

                      Если someSyncFun принимает не Promise, то, если не за-await-ить someAsyncFun1, ошибка будет. Но это практически бесполезная полумера. В принципе можно еще руками проставить тип resAsync1, тогда тоже будет ошибка, но об этом тоже нужно помнить.


                      1. LMnet
                        26.01.2017 12:30

                        Да, при несовпадении типов дальше по коду можно выловить часть ошибок. Но если функция с сайд эффектом и ничего не возвращает, а нам нужно дождаться этого сайд эффекта, то мы просто получим race condition, если забыли await. В моем примере выше на scala.js даже функция без возвращаемого значения (isUserValidAsync) завернута в for-comprehension и явно видно, что она является частью асинхронного блока.


                        1. raveclassic
                          26.01.2017 12:46

                          Понятное дело, я и не спорю, мне такой формат записи по душе. Увы, ни JS, ни TS не предоставляют таких строгих конструкций.


                        1. staticlab
                          26.01.2017 13:06

                          Если вы вместо _ <- isUserValidAsync(user) по ошибке напишете isUserValidAsync(user), что будет?


                          1. LMnet
                            26.01.2017 13:08

                            Не скомпилируется. for-comprehension всегда требует что-то возвращать в левую часть стрелки. Если возвращать нечего — то используется запись _ <- blabla