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

О JavaScript промисах я знал лишь в общих чертах, соответственно пришлось подыскать кое-какую литературу. Среди прочего я почитал вот эту статью "JavaScript Promises. There and back again." на html5rocks и нашел ее весьма познавательной и исчерпывающей. Конечно же, статья была на английском, как и большинство найденной мною информации. Я решил ею поделиться, однако, пришлось учитывать тот факт, что вопреки распространенному мнению, не все программисты в СНГ свободно владеют английским. Да и на родном языке всегда читать приятнее...

Вот так и получилось, что я стал переводить. Публиковать текст я буду по частям, поскольку статья довольно длинная, и перевод требует массу времени. Однако, я планирую выложить всю статью в течении недели (ну, максимум двух).

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

Дамы и господа, приготовьтесь к поворотному моменту в истории веб разработки...

[Барабанная дробь]

В JavaScript появилась поддержка промиcов(promises)!

[Взрывы фейерверков, серпантин и блестящая бумага падает с неба, и толпа сходит с ума]

В данном случаи Вы попадаете в одну их следующих категорий:

Люди вокруг Вас ликуют, но Вы не совсем уверены, по какому, собственно, поводу такая шумиха. Возможно, Вы даже не знаете, что такое эти "промисы". Вы бы просто пожали плечами и пошли дальше, но вес серпантина на Ваших плечах слишком велик. Если так, то не волнуйтесь, мне понадобились многие годы, для того, чтобы самому понять, почему меня это должно волновать. Пожалуй, Вам следует начать отсюда.
Вы вскидываете кулак в воздух. Давно пора, да? Вы уже использовали это промис-штуки раньше, но Вас беспокоит, что все имплементации имели несколько различный API. Какой же API у официальной JavaScript версии? Пожалуй, Вам стоит начать отсюда.
Вы уже знали об этом, и посмеиваетесь, над теми, что скачет вверх и вниз, как-будто это для них новость. Остановитесь на мгновение, чтоб насладиться своим превосходством, а затем направляйтесь прямиком к описанию API.

О чем же вся эта шумиха?



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

Как человеческое существо, Вы многопоточны. Вы можете набирать текст несколькими пальцами одновременно, Вы можете одновременно вести машину и поддерживать диалог. Единственной блокирующей функцией, с которой на приходится иметь дело является чиханье, при этом, все активные действия следует приостановить на время чиханья. Это сильно раздражает, особенно, когда Вы ведете машину, при этом пытаясь поддерживать диалог. Вам бы не хотелось писать "чихательный" код.
Вы , наверное, использовали пользовательские события и коллбеки, для того, чтобы обойти это. Например:

var img1 = document.querySelector('.img-1');

img1.addEventListener('load', function() {
// ура картинка загрузилась
});

img1.addEventListener('error', function() {
// черт, все поломалось
});


Это совершенно не "чихательно". Мы получаем картинку и привязываем несколько обработчиков, затем JavaScript прекращает выполнение до тех пор, пока один из обработчиков не будет вызван.
К сожалению, в предыдущем примере, возможно, что событие произошло до того, как мы привязали обработчик, соответственно, нам нужно обойти эту проблему, используя свойство "complete" картинки:

var img1 = document.querySelector('.img-1');

function loaded() {
// ура картинка загрузилась
}

if (img1.complete) {
loaded();
}
else {
img1.addEventListener('load', loaded);
}

img1.addEventListener('error', function() {
// черт, все поломалось
});


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

События не всегда лучший способ



События это отличный инструмент для случая, когда что-то происходит несколько раз с одним и тем же объектом — keyup, touchstart, и т. д. В случаи этих событий, Вам в целом не важно, что происходило до того, как мы привязали обработчик. Когда же дело касается асинхронного вызова, типа "успех/неудача", Вам бы хотелось чего-то в таком духе:

img1.callThisIfLoadedOrWhenLoaded(function() {
// загружено
}).orIfFailedCallThis(function() {
// неудача
});

// и…
whenAllTheseHaveLoaded([img1, img2]).callThis(function() {
// все загружено
}).orIfSomeFailedCallThis(function() {
// одна или более загрузок не удалась
});


Это как раз и есть то, для чего нужны промисы, только с лучшим именованием. Если бы у HTML картинок был метод "ready" возвращающий промис, мы могли бы делать так:

img1.ready().then(function() {
// загружено
}, function() {
// неудача
});

// и…
Promise.all([img1.ready(), img2.ready()]).then(function() {
// все загружено
}, function() {
// одна или более загрузок не удалась
});


На базовом уровне, промисы похожи на обработчики событий, за исключением:

1. Промис может завершится успехом или неудачей, всего один раз. Он не может как выполнится более одного раза, так и изменить свое состояние с успеха на неудачу или наоборот.
2. Если промис выполнился успешно или неудачно, а затем Вы добавляете к нему функцию для вызова в случаи успеха/неудачи, то соответствующая функция будет вызвана, даже в случаи, если событие уже произошло.

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

Комментарии

Для того, чтоб оставлять комментарии или зарегистрируйтесь.