У нас проблемы с промисами

от автора

Разрешите представить вам перевод статьи Нолана Лоусона «У нас проблемы с промисами», одной из лучших по теме из тех, что мне доводилось читать.

У нас проблемы с промисами

Дорогие JavaScript разработчики, настал момент признать это — у нас проблемы с промисами.

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

— Многие из нас используют промисы без действительного их понимания.

Если вы мне не верите, решите такую задачку:

Вопрос: В чем разница между этими четырьмя вариантами использования промисов?

doSomething().then(function () {   return doSomethingElse(); });  doSomething().then(function () {   doSomethingElse(); });  doSomething().then(doSomethingElse());  doSomething().then(doSomethingElse); 


Если вы знаете ответ, то разрешите вас поздравить — по части промисов вы нинзя. Пожалуй, дальше этот пост вам можно не читать.

Остальным 99,99% из вас я спешу сказать, чтобы вы не растраивались, вы в хорошей компании. Никто из тех, кто ответил на мой твит, не смог решить задачу. Даже я был очень удивлен ответом на 3-й вопрос. Да-да, несмотря на то, что это я его задал!

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

Прежде, чем начать, давайте обозначим некоторые моменты.

Почему промисы?

Если вы читаете статьи о промисах, то часто будете встречать отсылки на «пирамиду зла» («pyramide of doom» в ориг.), образованную ужасным кодом на колбэках, который растягивает страницу направо за экран.

Промисы действительно решают эту проблему, но это нечто большее, чем просто уменьшение отступов. Как объясняется в замечательной беседе «Спасение из ада колбэков», настоящая их проблема в том, что они лишают нас возможности использовать инструкции return и throw. Вместо этого логика наших программ основана на использовании побочных эффектов, когда одна функция вызывает другую.

Также колбэки делают что-то действительно зловещее, они лишают нас стека, того, что в языках программирования принимается как должное. Писать код без стека — это все равно, что вести автомобиль без педали тормоза. Вы не представляете, насколько она важна до тех пор, пока она вам действительно не понадобится и ее не окажется на месте.

Весь смысл промисов в том, чтобы вернуть нам основы языка, потерянные в момент нашего перехода на асинхронность: return, throw и стек. Но вы должны знать, как правильно использовать промисы, чтобы подняться в этом на более высокий уровень.

Ошибки новичков

Кто-то пытается объяснить промисы в виде мультика или, говоря словами: «О! Это штука, которую вы можете создать и передавать всюду, а она собой символизирует какое-то значение, получаемое и возвращаемое асинхронно».

Я не нахожу такое объяснение достаточно полезным. Для меня промисы — это всегда часть структуры кода, его потока выполнения.

Небольшое отступление: термин «промисы» для разных людей несет в себе разный смысл. В этой статье я буду рассказывать об официальной спецификации, доступной в современных браузерах как window.Promise. Для тех браузеров, которые не имеют window.Promise, есть хороший полифил с нахальным названием Lie (ложь), содержащий минимальную реализацию спецификации.

Ошибка новичка №1 — «пирамида зла» из промисов

Глядя, как люди используют PouchDB, API которого сильно завязано на промисах, я вижу немало плохих паттернов их использования. Вот наиболее распространенный пример:

remotedb.allDocs({   include_docs: true,   attachments: true }).then(function (result) {   var docs = result.rows;   docs.forEach(function(element) {     localdb.put(element.doc).then(function(response) {       alert("Pulled doc with id " + element.doc._id + " and added to local db.");     }).catch(function (err) {       if (err.status == 409) {         localdb.get(element.doc._id).then(function (resp) {           localdb.remove(resp._id, resp._rev).then(function (resp) { // и так далее… 

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

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

Вот вариант получше:

remotedb.allDocs(...)   .then(function (resultOfAllDocs) {     return localdb.put(...);   })   .then(function (resultOfPut) {     return localdb.get(...);   })   .then(function (resultOfGet) {     return localdb.put(...);   })   .catch(function (err) {     console.log(err);   }); 

В примере выше использовались составные промисы («composing promises» в ориг.) — одна из сильнейших сторон промисов. Каждая последующая функция будет вызвана, когда предыдущий промис «зарезолвится», и вызвана она будет с результатом работы предыдущего промиса. Подробности будут ниже.

Ошибка новичка №2 — как мне использовать forEach() с промисами?

Это тот момент, когда понимание промисов большинством людей начинает сдавать. Они хорошо знакомы с итераторами forEach, for или while, но не имеют ни малейшего представления, как сочетать их с промисами. Тогда рождается что-то подобное:

// Я хочу применить remove() ко всем документам db.allDocs({include_docs: true})   .then(function (result) {     result.rows.forEach(function (row) {       // Метод remove возвращает promise       db.remove(row.doc);     });   })   .then(function () {     // А здесь я наивно уверен, что все документы уже удалены!   }); 

Что не так с этим кодом? Проблема в том, что первая функция возвращает undefined, а значит вторая не ждет окончания выполнения db.remove() для всех документов. На самом деле она вообще ничего не ждет и выполнится, когда будет удалено любое число документов, а может и ни одного.

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

Подводя итог, скажу, что конструкции типа forEach, for и while «не те дроны, что вы ищете». Вам нужен Promise.all():

db.allDocs({include_docs: true})   .then(function (result) {     var arrayOfPromises = result.rows.map(function (row) {       return db.remove(row.doc);     });     return Promise.all(arrayOfPromises);   })   .then(function (arrayOfResults) {     // Вот теперь все документы точно удалены!   }); 

Что здесь происходит? Promise.all() принимает в качестве аргумента массив промисов и возвращает новый промис, который «зарезолвится», только когда все документы будут удалены. Это асинхронный эквивалент цикла for.

Также промис из Promise.all() передаст в следующую функцию массив результатов, что может быть очень удобно, если вы, например, не удаляете документы, а получаете данные сразу из нескольких источников. Если хотя бы один промис из массива, переданного в Promise.all() «зареджектится», то и результирующий промис перейдет в состояние rejected.

Ошибка новичка №3 — забываем добавлять .catch()

Это еще одна распространенная ошибка — блаженно верить, что ваши примисы никогда не вернут ошибку. Многие разработчики просто забывают добавлять catch() куда-либо в своем коде.

К сожалению, часто это означает, что ошибки будут «проглочены». Вы даже никогда не узнаете, что они были — особая боль при отладке приложения.

Чтобы избежать этого неприятного сценария, я взял за правило, которое затем переросло в привычку, всегда добавлять в конец моей цепочки промисов метод catch():

somePromise().then(function () {     return anotherPromise();   })   .then(function () {     return yetAnotherPromise();   })   // простое и полезное окончание цепочки промисов:   .catch(console.log.bind(console)); 

Даже если вы гарантированно, стопроцентно не ожидаете каких-либо ошибок, добавление catch() будет разумным решением. Потом, если вдруг ваше предположение насчет ошибок не оправдается, вы скажете себе спасибо.

Ошибка новичка №4 — использование «deferred»

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

Вкратце, в своем развитии промисы прошли долгую историю. У JavaScript сообщества ушло много времени на то, чтобы реализовать их правильно. Поначалу jQuery и Angular повсеместно использовали паттерн deferred-объектов, который впоследствии был заменен на спецификацию промисов ES6, в основе которой лежали «хорошие» библиотеки Q, When, RSVP, Bluebird, Lie и другие.

Вобщем, если вы вдруг написали это слово в своем коде (я не повторю его в третий раз!), знайте — вы что-то делаете не так. Ниже рецепт, как этого избежать.

Большинство «промисных» библиотек дают вам возможность импортировать промисы из других библиотек. Например, модуль $q из Angular позволяет вам обернуть не-$q промисы при помощи $q.when(). То есть пользователи Angular могут оборачивать промисы из PouchDB так:

// это все, что нужно: $q.when(db.put(doc)).then(/* ... */); 

Другой путь — использование паттерна раскрытия конструктора («revealing constructor pattern» в ориг.). Он удобен для оборачивания API, не использующего промисы. Например, чтобы обернуть основанное на колбэках API Node.js, вы можете сделать следующее:

new Promise(function (resolve, reject) {   fs.readFile('myfile.txt', function (err, file) {     if (err) {       return reject(err);     }     resolve(file);   }); }).then(/* ... */) 

Готово! Мы расправились с ужасным defer… Ах, чуть не произнес в третий раз! 🙂

Ошибка новичка №5 — использование внешних функций вместо возвращения результата

Что не так с этим кодом?

somePromise().then(function () {     someOtherPromise();   })   .then(function () {     // Ох, я надеюсь someOtherPromise «зарезолвился»…     // Осторожно, спойлер: нет, не «зарезолвился».   }); 

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

Серьезно, это тот самый трюк, поняв который, вы сами сможете избежать всех тех ошибок, о которых мы говорили. Вы готовы?

Как я уже упоминал, магия промисов в том, что они возвращают нам драгоценные return и throw. Но что это означает на практике?

Каждый промис предоставляет вам метод then() (а еще catch(), который на практике просто «сахар» для then(null, …)). И вот мы внутри функции then():

somePromise().then(function () {   // Вау, мы внутри функции then()! }); 

Что мы можем тут сделать? Три вещи:

  1. Вернуть (return) другой промис
  2. Вернуть (return) синхронное значение (или undefined)
  3. Выдать (throw) синхронную ошибку

Вот и все, весь трюк. Поймете его — поймете промисы. Давайте теперь разберем подробно каждый из пунктов.

1. Вернуть другой промис

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

getUserByName('nolan').then(function (user) {     // Функция getUserAccountById возвращает promise,     // результат которого попадет в следующий then     return getUserAccountById(user.id);   })   .then(function (userAccount) {     // Я знаю все о пользователе!   }); 

Обратите внимание, что я именно возвращаю второй промис, использую return. Использование здесь return — это ключевой момент. Если бы я просто вызвал getUserAccountById, то да, был бы запрос за данными пользователя, был бы получен результат, который нигде бы не пригодился — следующий then получил бы undefined вместо желанного userAccount.

2. Вернуть синхронное значение (или undefined)

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

getUserByName('nolan').then(function (user) {     if (inMemoryCache[user.id]) {       // Данные этого пользователя уже есть,       // возвращаем сразу       return inMemoryCache[user.id];     }     // А вот про этого пока не знаем,     // вернем промис запроса     return getUserAccountById(user.id);   })   .then(function (userAccount) {     // Я знаю все о пользователе!   }); 

Разве не круто? Второй функции в цепочке не важно, откуда взялись данные, из кэша или как результат запроса, а первая вольна вернуть или синхронное значение сразу, или асинхронный промис, который уже в свою очередь вернет синхронное значение.

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

Для себя я ввел правило, которое затем переросло в привычку — всегда использовать return внутри then или выдавать ошибку при помощи throw. Я рекомендую вам поступать так же.

3. Выдавать синхронную ошибку

Вот мы и подошли к throw. Здесь промисы начинают сиять еще ярче. Предположим, мы хотим выдать (throw) синхронную ошибку, если пользователь не авторизован. Это просто:

getUserByName('nolan').then(function (user) {     if (user.isLoggedOut()) {       // Пользователь вышел — выдаем ошибку!       throw new Error('user logged out!');     }     if (inMemoryCache[user.id]) {       // Данные этого пользователя уже есть,       // возвращаем сразу       return inMemoryCache[user.id];     }     // А вот про этого пока не знаем,     // вернем промис запроса     return getUserAccountById(user.id);   })   .then(function (userAccount) {     // Я знаю все о пользователе!   })   .catch(function (err) {     // Упс, ошибка, но мы к ней готовы!   }); 

Наш catch() получит синхронную ошибку, если пользователь не авторизован, или асинхронную, если любой из промисов выше перейдет в состояние rejected. И снова, функции в catch без разницы, была ошибка синхронной или асинхронной.

Это особенно удобно для отлавливания ошибок во время разработки. Например, формирование объекта из строки при помощи JSON.parse() где-либо внутри then() может выдать ошибку, если json невалидный. С колбэками она будет «проглочена», но при помощи метода catch() мы без труда сможем ее обработать.

Продвинутые ошибки

Хорошо, теперь, когда вы выучили главный трюк промисов, давайте поговорим о крайних случаях. Потому что крайние случаи есть всегда.

Эту категорию ошибок я называю «продвинутые», потому что встречал их только в коде хорошо знакомых с промисами программистов. Обсудить подобные ошибки мы должны для того, чтобы разобрать задачку, которую я опубликовал в самом начале статьи.

Продвинутая ошибка №1 — не знаем о Promise.resolve()

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

new Promise(function (resolve, reject) {   resolve(someSynchronousValue); }).then(/* ... */); 

Имейте в виду, вы можете написать то же самое гораздо короче:

Promise.resolve(someSynchronousValue).then(/* ... */); 

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

function somePromiseAPI() {   return Promise.resolve()     .then(function () {       doSomethingThatMayThrow();       return 'foo';     })     .then(/* ... */); } 

Просто запомните, любой код, который может выдать синхронную ошибку — потенциальная проблема при отладке из-за «проглоченных» ошибок. Но если вы обернете его в Promise.resolve(), то можете быть уверены, что поймаете ее при помощи catch().

Еще есть Promise.reject(). Его можно использовать для возвращения промиса в статусе rejected:

Promise.reject(new Error(‘Какая-то ужасная ошибка’)); 

Продвинутая ошибка №2 — catch() не одно и то же с then(null, …)

Чуть выше я упоминал, что catch() — это просто «сахар». Два примера ниже эквивалентны:

somePromise().catch(function (err) {   // Обрабатываем ошибку });  somePromise().then(null, function (err) {   // Обрабатываем ошибку }); 

Однако, примеры ниже уже не «равны»:

somePromise().then(function () {     return someOtherPromise();   })   .catch(function (err) {     // Обработка ошибка   });  somePromise().then(function () {   return someOtherPromise(); }, function (err) {   // Обработка ошибки }); 

Если вы задумались, почему примеры выше «не равны», посмотрите внимательно, что произойдет, если в первой функции возникнет ошибка:

somePromise().then(function () {     throw new Error('oh noes');   })   .catch(function (err) {     // Ошибка поймана! :)   });  somePromise().then(function () {   throw new Error('oh noes'); }, function (err) {   // Ошибка? Какая ошибка? O_o }); 

Получается, что, если вы используете формат then(resolveHandler, rejectHandler), то rejectHandler по факту не может поймать ошибку, возникшую внутри функции resolveHandler.

Зная эту особенность, для себя я ввел правило никогда не использовать вторую функцию в методе then(), а взамен всегда добавлять обработку ошибок ниже в виде catch(). Исключение у меня только одно — асинхронные тесты в Mocha, в случаях, когда я намеренно жду ошибку:

it('should throw an error', function () {   return doSomethingThatThrows().then(function () {     throw new Error('I expected an error!');   }, function (err) {     should.exist(err);   }); }); 

К слову, Mocha и Chai — отличная комбинация для тестирования основанного на промисах API.

Продвинутая ошибка №3 — промисы против фабрик промисов

Допустим, вы хотите выполнить серию промисов один за другим, последовательно. Вы хотите что-то вроде Promise.all(), но такой, чтобы не выполнял промисы параллельно.

Сгоряча вы можете написать что-то подобное:

function executeSequentially(promises) {   var result = Promise.resolve();   promises.forEach(function (promise) {     result = result.then(promise);   });   return result; } 

К сожалению, пример выше не будет работать так, как задумывалось. Промисы из списка, переданного в executeSequentially(), все равно начнут выполняться параллельно.

Причина в том, что по спецификации промис начинает выполнять заложенную в него логику сразу после создания. Он не будет ждать. Таким образом, не сами промисы, а массив фабрик промисов — это то, что действительно нужно передать в executeSequentially:

function executeSequentially(promiseFactories) {   var result = Promise.resolve();   promiseFactories.forEach(function (promiseFactory) {     result = result.then(promiseFactory);   });   return result; } 

Я знаю, вы сейчас думаете: «Кто, черт возьми, этот Java программист, и почему он рассказывает нам о фабриках?». На самом деле фабрика — это простая функция, возвращающая промис:

function myPromiseFactory() {   return somethingThatCreatesAPromise(); } 

Почему этот пример будет работать? А потому, что наша фабрика не создаст промис до тех пор, пока до него не дойдет очередь. Она работает именно как resolveHandler для then().

Посмотрите внимательно на функцию executeSequentially() и мысленно замените ссылку на promiseFactory ее содержимым — сейчас над вашей головой должна радостно вспыхнуть лампочка 🙂

Продвинутая ошибка №4 — что, если я хочу результат двух промисов?

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

getUserByName('nolan').then(function (user) {     return getUserAccountById(user.id);   })   .then(function (userAccount) {     // Стойте, мне еще и объект «user» нужен!   }); 

Желая оставаться хорошими JavaScript разработчиками, мы возможно захотим вынести на более высокий уровень видимости переменную user, дабы не создавать «пирамиду зла».

var user; getUserByName('nolan').then(function (result) {     user = result;     return getUserAccountById(user.id);   })   .then(function (userAccount) {     // Хорошо, теперь есть и «user», и «userAccount»   }); 

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

getUserByName('nolan').then(function (user) {     return getUserAccountById(user.id)       .then(function (userAccount) {         // Хорошо, теперь есть и «user», и «userAccount»       }); }); 

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

function onGetUserAndUserAccount(user, userAccount) {   return doSomething(user, userAccount); }  function onGetUser(user) {   return getUserAccountById(user.id)     .then(function (userAccount) {       return onGetUserAndUserAccount(user, userAccount);     }); }  getUserByName('nolan')   .then(onGetUser)   .then(function () {     // К этому моменту функция doSomething() выполнилась,     // а отступы не выросли — пирамидой и не пахнет   }); 

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

putYourRightFootIn()   .then(putYourRightFootOut)   .then(putYourRightFootIn)   .then(shakeItAllAbout); 

Вот для чего нам промисы.

Продвинутая ошибка №5 — «проваливание» сквозь промисы

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

Как вы думаете, что выведет в консоль этот код?

Promise.resolve('foo')   .then(Promise.resolve('bar'))   .then(function (result) {     console.log(result);   }); 

Если вы думаете, что это будет bar, вы ошиблись. В консоли появится foo!

Причина в том, что, когда вы передаете в then() что-то отличное от функции (например, промис), это интерпретируется как then(null) и в следующий по цепочке промис «проваливается» результат предыдущего. Проверьте сами:

Promise.resolve('foo')   .then(null)   .then(function (result) {     console.log(result);   }); 

Добавьте сколько угодно then(null), результат останется прежним — в консоли вы увидите foo.

Данный пример возвращает нас к выбору между промисами и фабриками промисов. Мы разбирали его выше. Если коротко, вы можете передавать прямо в then() промис, но результат будет совсем не тем, что вы ожидали. Метод then() ожидает функцию. Чтобы ожидания сбылись, нужно переписать пример как-то так:

Promise.resolve('foo')   .then(function () {     return Promise.resolve('bar');   })   .then(function (result) {     console.log(result);   }); 

В консоли вы увидите bar, как и ожидали.

Запоминаем: в метод then() передаем только функции.

Решение задачи

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

Вот ответы на все пазлы с визуализацией для лучшего понимания.

Пазл №1

doSomething().then(function () {     return doSomethingElse();   })   .then(finalHandler); 

Ответ:

doSomething |-----------------|                   doSomethingElse(undefined)                   |------------------|                                      finalHandler(resultOfDoSomethingElse)                                      |------------------| 

Пазл №2

doSomething().then(function () {     doSomethingElse();   })   .then(finalHandler); 

Ответ:

doSomething |-----------------|                   doSomethingElse(undefined)                   |------------------|                   finalHandler(undefined)                   |------------------| 

Пазл №3

doSomething().then(doSomethingElse())   .then(finalHandler); 

Ответ:

doSomething |-----------------| doSomethingElse(undefined) |---------------------------------|                   finalHandler(resultOfDoSomething)                   |------------------| 

Пазл №4

doSomething().then(doSomethingElse)   .then(finalHandler); 

Ответ:

doSomething |-----------------|                   doSomethingElse(resultOfDoSomething)                   |------------------|                                      finalHandler(resultOfDoSomethingElse)                                      |------------------| 

Если вы не поняли объяснения, то я советую вам перечитать статью еще раз, или самостоятельно написать функции doSomething() и doSomethingElse(), и попэкспериментировать с ними в браузере. Подразумевается, что эти функции возвращают промисы с какими-то результирующими данными.

Также обратите внимание на мой список полезных заготовок.

Заключительное слово о промисах

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

Если вы мне не верите, держите пруф-линк — рефакторинг PounchDB модуля map/reduce с заменой колбэков на промисы. Результат: 290 добавленных строк, 555 удаленных. По-случайности, автором всех этих прежних жутких колбэков был… я. Так что это стало для меня первым из освоенных преимуществ промисов.

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

Ждем async/await

В своей статье «Укрощение асинхронного чудовища с ES7» я упоминал async/await и то, как они глубже интегрировали промисы в язык. Вместо написания псевдо-синхронного кода (с фейковым методом catch(), который как из try/catch, но не совсем, ES7 позволит нам использовать настоящие try/catch/return.

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

В качестве примера из истории JavaScript, я думаю, что JSLint и JSHint сделали для сообщества гораздо больше, чем «Хорошие стороны JavaScript», хоть в целом суть у них похожа. Разница в том, что в первом случае вам говорят о конкретной ошибке, которую вы сделали в определенной строке, а во втором вы читаете большую книгу о том, какие ошибки делают другие разработчики.

Красота async/await в основном в том, что ваши ошибки проявят себя сразу при синтаксическом анализе в интерпретаторе JS, а не когда-то возможно где-то во время исполнения кода. До тех же пор полезным будет представление о возможностях промисов, о том, как их использовать в ES5 и ES6.

Как и книга «Хорошие стороны JavaScript», эта статья имеет малый эффект. Вероятно однажды вы сможете дать на нее ссылку своему коллеге, который найдет в себе силы честно признать:

— У меня проблемы с промисами!

ссылка на оригинал статьи http://habrahabr.ru/post/269465/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *