Комментарии 61
Я с JavaScript сталкивался в 2004 году, потому мне полезно =)
Интересно, а рассказы про промисы ещё актуальны? (Не сарказм, просто иногда подумываю, а не написать ли и мне статью, где я всё всем разжую про них. Однако боюсь что все уже знают и закидают шапками типа "добро пожаловать в 2012")
async/await при том не отменяют Promise, а скорее дополняют оные.
Имхо, конечно.
только кому-то достаточно прочитать документацию, кому-то ее может не хватить, возможно, из-за отсутствия тех примеров, которые будут понятны. статьи же пишут с целью поделиться опытом, который другим может помочь лучше разобраться в той или иной теме. иначе, можно всегда ссылаться на документацию, и не только по этой теме, а вообще.
Singapura, у Вас странное написание слов: принято писать не «по-битово» а «побитово»,
не «выжал-бы» а «выжал бы».
Если же Вы пытались поддеть RA_ZeroTech, то зря: фразы «в чём-то» «кому-то» и «как-то так» «кем-то» пишутся именно так, с дефисом.
В результате вызова myPromise() все равно сработал бы метод then() или catch(). Лучше всего завести сразу привычку — всегда возвращать resolve(...) или reject(...). В будущем это поможет избежать ситуации, когда код будет работать не так, как ожидается.
Можете пример привести?
function myPromiseRejected()
{
return new Promise(function (resolve, reject)
{
var err = 'error';
if (err)
reject('Promise rejected');
resolve('Promise resolved');
});
}
function myPromiseRejected2()
{
return new Promise(function (resolve, reject)
{
var err = 'error';
if (err)
return reject('Promise rejected2');
return resolve('Promise resolved2');
});
}
myPromiseRejected()
.catch(function(err){
console.log(err);
});
myPromiseRejected2()
.catch(function(err){
console.log(err);
});
В будущем это поможет избежать ситуации, когда код будет работать не так, как ожидается.
Этот фраза больше относилась к тем ситуациям, когда забывают возвращать «промис» в своих методах. При этом экспешина не возникает, например:
function myPromise1()
{
return new Promise(function (resolve, reject)
{
var err = false;
if (err)
reject('Promise rejected');
resolve('Promise resolved');
});
}
function myPromise2()
{
new Promise(function (resolve, reject)
{
var err = false;
if (err)
return reject('Promise rejected2');
return resolve('Promise resolved2');
});
}
myPromise1()
.then(function (res)
{
console.log(res);
return myPromise2();
})
.then(function (res)
{
//undefined, хотя долнжо показать "Promise resolved". потому что в myPromise2() не вернули промис
console.log(res);
})
.catch(function(err){
console.log(err);
});
function myPromise1()
{
return new Promise(function (resolve, reject)
{
var err = true;
if (err)
reject('Promise rejected');
console.log('у нас же "ошибка", почему оказались здесь?');
resolve('Promise resolved');
});
}
myPromise1()
.then(function (res)
{
console.log(res);
})
.catch(function(err){
console.log(err);
});
отчасти, и по этому тоже советовал всегда возвращать return resolve() или reject(), так как по логике, после вызовов этих методов, следующий за ними код не должен вызываться. иначе можно запутать самого себя, разбираясь в чем же дело…
PS. да, есть исключение для этого совета: если эти методы вызываются в качестве колбэков. как правильно заметил Apathetic
console.log('у нас же «ошибка», почему оказались здесь?');
потому что промис — это конечный автомат, вызов reject/resolve меняет состояние, но не завершает выполнение текущей функции.
вот попробуйте:
new Promise((resolve, reject) => {
setTimeout(() => {
console.log('111111');
resolve();
setTimeout(() => {
console.log('222222');
});
});
}).then(console.log.bind(null, '333333'));
Вот еще можно глянуть: https://www.promisejs.org/implementing/
потому что промис — это конечный автомат, вызов reject/resolve меняет состояние, но не завершает выполнение текущей функции.
совершенно верно :) именно это я и показал в примере
Поэтому стоит использовать полифилы…
novrm рекомендует bluebird. Я, в свою очередь, тоже ей пользуюсь, в том числе и при написании серверного JS (NodeJS). Достаточно много полезных методов реализовано.
А не лучше ли использовать какой ни будь полифил, который добавляем только объект window.Promise, и только те методы которые гарантированно присутствуют в браузерах. Ибо если вы завязываетесь на bluebird и на методы которых нет в реализациях браузеров — вы завязываетесь на кастомную реализацию промисов, а не полифилите реализацию браузера.
Как она будет реализована — другое дело…
Но главное — должны быть полностью соблюдены рекомендации, что присутствует в bluebird, но отсутствует в том же jQuery…
Bluebird Promise да, поддерживает стандарт, но и выходят за его рамки. Если тебе нужен полифил, то тебе нужен полифил со стандартным набором методов. Потому как полифил нужен ровно до того момента, когда он становится не нужным. Тогда вы выкидываете полифил в пользу браузерной реализации. Если вы взяли в качестве "полифила" Bluebird и начали использовать методы не входящие в стандарт — выкинуть такой "полифил" не получится, и в этом случае полифил перестает быть полифилом.
Имею ввиду — использовать «стандартный» набор методов.
Если хотите, вот пример возможной реализации (es6).
/**
* Import bluebird plugin.
*
* @link https://github.com/petkaantonov/bluebird/
* @link http://bluebirdjs.com/docs/api-reference.html
*/
import BowerAssetBluebirdPlugin from 'asset/bluebird/js/browser/bluebird.min';
'use strict';
/**
* Promise class wrapper.
*/
classLoader.autoload['BundleFramework/Promise/Promise'] = (function () {
/**
* Set private properties.
*/
let _plugin = BowerAssetBluebirdPlugin;
/**
* Promise class.
*/
return class {
/**
* Constructor. Create an instance.
*
* @param object config
*/
constructor(config = Object(config = {})) {
Object.freeze(this);
};
/**
* Ajax action.
*
* @link http://bluebirdjs.com/docs/coming-from-other-libraries.html#coming-from-jquery-deferreds
* @param object options
* @return object new _plugin
*/
ajax(options = Object(options = {})) {
let plugin = this.getPlugin();
return new plugin(function(resolve, reject) {
$.ajax(options).done(resolve).fail(reject);
});
};
getPlugin() {
return _plugin;
};
};
})();
export {classLoader};
По-моему, достаточно подключить глобально файл-полифил и использовать Promise
как часть глобального окружения (global
, window
). По-моему, это как раз и идея полифилов "доукомплектовать окружение отсутствующими в данной версии браузера компонентами".
В вашем же случаи, насколько я смог понять, используется какой то кастомный прелоадер компонентов, через который вы и получаете доступ к Promise
. В общем то, реализацию можно будет подменить и через него, но используя Bluebird вы (или ваши коллеги) рискуете завязаться на нестандартные методы.
И еще кейс, если вы будете исользовать стороннюю библиотеку, которая не знает о вашем прелоадере компонентов — а в браузере внезапно в глобальном скопе нет Promise
— библиотека не заработает.
Разработчики Bluebird как раз следуют стандарту…
Ну а «сахар» плагина — можете не использовать. Или явно его «выключить» — создав обертку над Bluebird (пример реализации которого я привел выше).
Кроме того — что такое стандарт? Это просто рекомендации…
Возможно «сахар» Bluebird завтра станет этим самым стандартом.
Или явно его «выключить» — создав обертку над Bluebird (пример реализации которого я привел выше).
Обертка плоха, потому что использует систему резолвинга зависимостей, наподобие requirejs. Подключаешь стороннюю библиотеку, которая ожидает window.Promise
и она не работает, например, в относительно стареньких браузерах. Поэтому нужен полифил который как раз создает недостающий объект окружения window.Promise
.
Возможно «сахар» Bluebird завтра станет этим самым стандартом.
Стандарт принимает рабочая группа. И если там нет в планах добавлять методы из Bluebird, значит их не добавят.
Возможно «сахар» Bluebird завтра станет этим самым стандартом.
Например через пару лет мы захотим выкинуть поддержку браузеров где нет промисов, проводим рефакторинг и принимаем решение выкинуть полифил Bluebird Promise
. Смотрим в код, и видим, что пару программистов заюзали по всему проекту его "сахар". Получится ли в этом случае выкинуть такой "полифил"?
Через несколько лет относительно стареньких браузеров никто поддерживать не станет.
Более того, возможно через пару лет и самих промисов не станет…
Их заменят чем то еще более универсальным… Например — генераторами.
Именно потому (ИМХО) резонно использовать «обертки» над технологиями…
Дабы отделить их использование от реализации.
А если вы желаете использовать некую стороннюю библиотеку, которая в свою очередь ожидает window.Promise — это проблема это библиотеки, что она внутри не реализует полифил…
Всегда можно найти библиотеку, которая реализует полифил или самому ее допилить…
Именно потому (ИМХО) резонно использовать «обертки» над технологиями…
А если вы желаете использовать некую стороннюю библиотеку, которая в свою очередь ожидает window.Promise — это проблема это библиотеки, что она внутри не реализует полифил…
Она не обязана, если библиотека ожидает современное окружение. Ваша цель — предоставить соответствующее окружение, или сэмулировать его. В идеальном мире все окружение должно быть описано в зависимостях, но исторически такого механизма у нас нет. То есть, мы привыкли, что у нас есть window
, localStorage
, location
. Конечно в идеале необходимо сделать декроторы для каждого, но часто это избыточно.
Знаете, вы так мыслите, как будто ваш код будет работать без изменений 100 лет.
Ну 100 лет преувеличено, но десятки лет — это почти реальность.
Не спорю, но если производительность Promise
не сильно важна, я предпочитаю обстрагироваться и использовать нативные Promise
или легкие полифилы. Потому как производительность нативной реализации могут существенно улучшить.
Просто в Bluebird, как я и писал, не нравится что API выходит за пределы стандарта, с одной стороны это хорошо, но как по мне, выглядит странно.
Всё это дело понеслось, а тут пользователь нажимает на ссылку, старый контент уже никому не нужен, нужно загружать новый. Окей, создаём новый промис, повторяем накидывание обработчиков. Постойте-ка, у нас в процессе выполнения старый. И тут оказывается, что у Promises/A+ нет никаких вариантов обработки отмены промиса.
Нам остаётся три пути:
- В каждом обработчике проверять, а не устарел ли запрос на действие, может результат уже никому не нужен.
- Сделать так, чтобы результат выполнения ВСЕГО промиса был проигнорирован (хотя и выполнен).
- Использовать 3rd party промисы или пилить свои.
Пока не особо думал об этом, на данный момент отошёл от промисов, давно не использовал, но пока выглядит так, что отмена должна найти стык, где промис ещё в процессе резолвинга, подменить ему then'ы на пустышки, и послать внутрь самого промиса сигнал отмены, который он может обработать, чтобы прекратить своё выполнение, если может (к примеру, если там промис, который просто спит сколько-то времени, то в обработчике отмены он может просто сделать clearTimeout).
Это всё ещё можно снабдить специальным сахаром для обработки отмен, чтобы можно было обрамлять некоторые куски цепочки операций, чтобы обрабатывать некоторые специфические случаи.
К примеру, есть метод, удаляющий некий item, возвращающий промис о завершении. Внутри он отправляет запрос на сервер, плюс делает какие-то действия с моделью. Пользователь жмёт «удалить», метод вызывается, промис начинает работу. Тут пользователь снова жмёт «удалить», очень резко, настолько резко, что запрос даже не успел уйти на сервер (окей, может, у нас есть какая-нибудь логика группировки запросов, или окна общения с сервером, поэтому он не уходит сразу). Тогда мы должны отменить промис удаления. Но произвёв отмену, нам бы хорошо знать, ушёл запрос на сервер или нет. Поэтому мы можем вставить в место, где совершается непосредственно запрос, специальный обработчик, например, .onCancel, который вызовется только когда чейн промисов в состоянии отмены и в него передастся, какое состояние у промиса, на который он непосредственно был накинут (т.е. начал он резолвится или ещё нет). Где каким-то образом куда-нибудь сообщим, что да как. И, например, если запрос уже ушёл, то нам нужно посылать второй в догонку, мол, «сервер, пользователь передумал это удалять, верни как было, пожалуйста».
Я пока не могу придумать, как можно типизированно сделать, чтобы тот, кто инициировал отмену мог подоставать данные из этих обработчиков с любой глубины, ибо структура чейна промисов может быть совсем любая, и мы не всегда сможем сказать, какие данные наши, какие не наши (например, если в чейне делается несколько запросов, у всех из которых есть обработчики отмены, генерирующие данные, мы отменяем чейн и хотим в результатате операции отмены знать результат отмены конкретного типа запроса). Пока думается, что можно при инициации отмены передавать таблицу Symbol→обработчик, а потом очень аккуратно и обдуманно в обработчиках отмен посылать туда данные, если в сигнале отмены в таблице присутствует некоторый символ.
Ох, что-то случайно прорвало.
Опечатка, жмёт «отменить удаление».
У вас конкретная специфическая задача — прерывать длинную синхронную функцию (которая внутри асинхронная вся такая, но выглядит синхронно), но в этой функции нет цикла — она просто очень длинная.
Такая же проблема у вас бы была, если бы у вас была самая обычная функция на много строк, и не мешало бы время от времени проверять, а не стоил ли ее завершить.
Хорошо, но в случае с await, или тем же .then из es6, у вас уже есть отличная точка входа для абстракции. Вам нужно конкретно под ваш длинный загрузчик из колбеков написать wrap для .then (или await), который будет проверять, необходимо ли прерывать код, и если да — просто не вызывать новый .then (awai).
Поправьте, если я где-то ошибся.
.then(checkedForCancel(function(data) { ... }))
, где checkedForCancel будет возвращать функцию, которая будет проверять какую-нибудь переменную, и только если она всё ещё хорошая, выполнять переданную.если я все правильно понял, то предположу, что задача похожа на «загрузить файл на сервер с возможностью отменить загрузку пока идет процесс»… Если предположение с аналогией верно, то такую задачу (правда на NodeJS), решал именно с помощью обработки событий.
Их можно отменить(отписаться), они умеют генерировать несколько событий, а не строго одно — например вернуть промежуточный результат
Не понимаю, почему из коробки нет аналога async-waterfall из nodejs и приходится постоянно копипастить его самому. А еще мне не нравится, что выполнение промиса начинается при его создании. Приходится постоянно городить обертки, если работаешь с набором задач
Эм, async поддерживает работу на стороне браузера.
Async is a utility module which provides straight-forward, powerful functions for working with asynchronous JavaScript. Although originally designed for use with Node.js and installable via npm install --save async, it can also be used directly in the browser.
Я понимаю что его можно использовать использовать в браузере, однако я говорю о наличии данной функции из коробки. Не всегда есть возможность подключать лишние js библиотеки
поправьте пожалуйста:
return return resolve(1);
на
reslove(1);
Параметр function onSuccess(){} будет вызван в случае успешного выполнения «обещания», function onFail(){} – в случае ошибки. По этой причине следующий код будет работать одинаково:
…
Гораздо привычнее и понятнее использовать catch(...).
Дело не в "привычном" и "непривычном", а в том, что последовательность ловли исключений меняется. Если функция onSuccess выбросит исключение, то в onFail вы это исключение не увидите. Зато увидите в последующем catch.
Иногда ошибочно кладут внешний callback в последние then и catch одновременно (для результатов или ошибки соответственно), что приводит к двойному вызову callback, если дальше по коду будет синхронно выброшено исключение. Пример такой ошибки — https://github.com/caolan/async/pull/1197
function loadImage(url){
return fetch(url)
.then(response=>response.blob())
.then(blob=>{
let img = new Image();
let imgSRC = URL.createObjectURL(blob);
img.src = imgSRC;
return img;
})
.catch(err => {
console.log('loadImage error: ', err, url)
throw err;
});
}
function loadAndDisplayImages(imgList){
let notLoaded = [];
let loaded = [];
let promiseImgs = imgList.map(loadImage);
return promiseImgs.reduce((prev, curr)=>{
return prev
.then(() => curr)
.then(img => {
loaded.push(img);
document.body.appendChild(img)
})
.catch(err => {
notLoaded.push(err);
console.log('loadAndDisplayImages error: ', err)
});
}, Promise.resolve())
.then(() => ({'loaded' : loaded, 'notLoaded': notLoaded}));
}
loadAndDisplayImages([
'https://hsto.org/getpro/habr/avatars/7ad/1ce/310/7ad1ce31064020bdb79dd73c755ad5ff_small.jpg',
'https://hsto.org/getpro/habr/avatars/51e/115/c17/51e115c17cbd25fb4adb16c1e3255a32_small.jpg',
'https://bad_url',
'https://hsto.org/getpro/habr/avatars/479/a6e/98d/479a6e98d816f8644ff18513cc26a60e_small.png'
]).then(console.log);
Мне кажется пример с «fetch» с небольшим описанием был бы «изюминкой» вашей статьи. Про «fetch» всего одна статья на Хабре, в отличие от промисов.
Путеводитель по JavaScript Promise для новичков