> Оригинал статьи


Представим что мы разрабатываем функцию isPi:


function isPi(x) {
  return x === 3.14
}

И тут кто-то говорит:?"Мы не хотим знать откуда приходят данные (БД, сеть и т.д.), по-этому давайте обернем наш код в Promise":



function isPi(px) {
  return px.then(x =>
    return x === 3.14
  })
}

И будем вызывать функцию так:


const px = grabXfromDb()
isPi(px)

– круто, да? – Нет


Проблема в том, что утверждение: "Мы оборачиваем код в Promise, чтобы не парится асинхронный код или синхронный" – не верно. Мы не оборачиаем код, но Promise это "что-то", что может:


  • либо "разрешиться" с значением
  • либо "сорваться" с ошибкой

когда-нибудь в будущем:


px.then(x =>
  // в этом месте x содержит уже известное "синхронное" значение
  isPi(x)
)

А значит мы можем удалить избыточный код:


function isPi(x) {
  return x === 3.14
}

const px = grabXfromDb()
px.then(isPi)

Вы можете спросить: ну ладно, начальный код чуть избыточен, что в этом плохого? Проблема возникает когда мы пытаемся добавить обратку ошибок – очень легко закончить с чем-то вроде:


function isPi(px) {
  return px.then(x =>
    return x === 3.14
  })
  .catch(fuction(e) {
    assert(e instanceof SomeDBError)
    // какой-нибудь код на случай ошибки базы данных
  })
}

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


function isPi(x) {
  return x === 3.14
}

grabXfromDb()
  .catch(fuction(e) {
    assert(e instanceof SomeDBError)
    // some code here 
  })
.then(isPi)

Несколько простых правил:


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

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


  1. napa3um
    28.04.2017 14:18

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

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


    1. HaMI
      28.04.2017 15:17
      -1

      Простите, но у меня сложилось впечатление что вы не читали статью и не понимаете значение слова «пример».


  1. rumkin
    28.04.2017 14:44
    +2

    М… Даже странно как-то видеть такую статью. Промис в принципе не должен передаваться как аргумент, если это не функция для работы с промисами, такая как Promise.all или Promise.race. Так же как и скрываться ошибка из промиса не должна внутри библиотечной функции.


    1. HaMI
      28.04.2017 15:19
      +1

      Аминь. Но, к сожалению, это не для всех очевидно – статья это попытка «доказать» ваше утверждение


  1. Nec1ord
    28.04.2017 15:07
    -1

    Полезная статья


  1. PaulMaly
    29.04.2017 11:52
    +1

    Никогда не видел, чтобы кто-то так писал. Возвращать промисс это да, но передавать параметром как-то не принято. Может быть мне везло.


  1. Apx
    30.04.2017 12:52

    Ну иногда видел похожие вещи в коде. Зачастую такой код любят писать c promise.All Читать может и красиво а вот с реверс инженирингом начинается батхерт. Если помнить одно простое правило что промис чейнится можно спокойно писать обычные функции и вызывать цепочку. И вот такой огород не нужен.
    Единственное когда я таким вот сам лично грешу это когда пишу стабы для апи которого ещё нет. Так сразу видно другим людям что тут когда-то будет апи вызов.