Комментарии 47
В недостатки нативных ES6 promises я бы еще добавил отсутствие метода finally() и не очень интуитивно понятный способ создания зарезолвленых промисов (Promise.all([])). С другой стороны, выкидывать лишние библиотеки из проектов всегда приятно :)
-1
Можете пояснить, про Promise.all — для создания заресолвленного Promise нужно просто вызвать Promise.resolve?
+1
не знаю, как в стандарте, а в jQuery можно вроде $.then(true).
0
.finally() можно заменить конструкцией .then().catch().then() и не выбрасывать exception заново в catch(), тогда будет считаться, что блок отлова ошибок ошибку поймал, обработал и вернул выполнение в нормальное русло.
По стандарту каждый блок .then() или .catch() выдает наружу новый promose, соотв. если в .catch() не перепрокинуть ошибку
и вернуть некий результат или другой primise, то будет считаться, что нормальный ход выполнения восстановлен.
По стандарту каждый блок .then() или .catch() выдает наружу новый promose, соотв. если в .catch() не перепрокинуть ошибку
.catch(function(e){
// do something
throw e;
})
и вернуть некий результат или другой primise, то будет считаться, что нормальный ход выполнения восстановлен.
0
Эх, если уж так хотите es6 — берите co+генераторы, они куда лучше. Откуда эта истерия про то что промисы — панацея?
Да, промисы помогают решать мелкие полезные задачи, но общий workflow на них проектировать — себе дороже.
Я это понял в один момент, когда реализовывал на них авторизацию на отзываемых токенах со сроком действия.
Было как-то так
Только кода еще больше было нагромождено. Промисы хороши там, где идет цепочечная обработка данных, не более. Если сложная логика — всегда будет куча замыканий даже с промисами.
на генераторах же будет как-то так
(я, правда, не пользовался ни разу возвращаемым значением, сделал код так более понятным, как мне кажется).
И как вы понятную и человекочитаемую рекурсию на промисах сделаете?
У меня регулярно бывают задачи по выборке из базы данных с рекурсивными условиями (сейчас, например — взять первые n элементов, отсортированных по приоритету, с условием, что берутся все элементы с заданным приоритетом, и общая выборка не меньше определенного числа), их на промисах охренеешь делать.
В общем промисы — да, это удобно. Но перегрето. Полгода-год назад вообще какая-то истерия про них была, непонятная для меня.
Да, промисы помогают решать мелкие полезные задачи, но общий workflow на них проектировать — себе дороже.
Я это понял в один момент, когда реализовывал на них авторизацию на отзываемых токенах со сроком действия.
Было как-то так
getTokenInfoFor(token).then(function(tokenInfo){
getUserInfo(tokenInfo.email).then(function(userInfo){
if (userInfo.tokenRevokeTime > tokenInfo.issueTime)
return Q.resolve(userInfo);
else
return Q.reject(errors.tokenRevoked);
});
});
Только кода еще больше было нагромождено. Промисы хороши там, где идет цепочечная обработка данных, не более. Если сложная логика — всегда будет куча замыканий даже с промисами.
на генераторах же будет как-то так
co(function*(){
var tokenInfo = yield getTokenInfoFor(token);
var userInfo = yield getUserInfo(tokenInfo.email);
if (userInfo.tokenRevokeTime > tokenInfo.issueTime)
throw e;
return userInfo;
})(function(err, res){
...
});
(я, правда, не пользовался ни разу возвращаемым значением, сделал код так более понятным, как мне кажется).
И как вы понятную и человекочитаемую рекурсию на промисах сделаете?
У меня регулярно бывают задачи по выборке из базы данных с рекурсивными условиями (сейчас, например — взять первые n элементов, отсортированных по приоритету, с условием, что берутся все элементы с заданным приоритетом, и общая выборка не меньше определенного числа), их на промисах охренеешь делать.
В общем промисы — да, это удобно. Но перегрето. Полгода-год назад вообще какая-то истерия про них была, непонятная для меня.
+3
Как программист C#, я испытываю боль от одного взгляда на промисы. В дотнете промисам и сопрограммам соответствуют таски (Task) и асинхронные методы (async/await). Однажды попробовав второе, возвращаться к первому нет никакого желания. Ну да, оба лучше, чем колбэки, но всё равно ведь ужас, если вызовы не в цепочку. Читать невозможно.
+4
Для node.js есть реализация async/await: github.com/yortus/asyncawait. Однажды попробовав, промисы использоваться больше не захочется.
+1
в traceur тоже есть вроде как, экспериментальная. но мне что-то Co+генераторы удобнее будет
0
Тащить async/await в js — это уже перебор, пусть этот ужас останется в .NET. В js есть множество своих методов и паттернов работы с callback'ами. По мне так async выполняет туже работу, что и asyncawait, только в более близком к js стилю.
-2
Что такого ужасного в async/await?
Синтаксис 1 в 1 такой же, как и с генераторами (которых все уже ждут-не дождутся), только возможностей больше, да и доступы уже сейчас.
Пример подсчета файлов в обоих случаях.
Как можно радоваться от промисов при виде этого стройного кода я не понимаю.
Причем даже в сложных ситуациях это все не разваливается а продолжает выглядеть понятно, как синхронный код. О промисах и callback'ах с async такого сказать нельзя.
Синтаксис 1 в 1 такой же, как и с генераторами (которых все уже ждут-не дождутся), только возможностей больше, да и доступы уже сейчас.
Пример подсчета файлов в обоих случаях.
// все нужные require
// co + генераторы
var countFiles = co(function* (dir) {
var files = yield fs.readdirSync(dir);
var paths = _.map(files, function (file) { return path.join(dir, file); });
var stats = yield _.map(paths, function (path) { return fs.statAsync(path); });
return _.filter(stats, function (stat) { return stat.isFile(); }).length;
});
// async/await
var countFiles = async.cps (function (dir) {
var files = await (fs.readdirSync(dir));
var paths = _.map(files, function (file) { return path.join(dir, file); });
var stats = await (_.map(paths, function (path) { return fs.statAsync(path); }));
return _.filter(stats, function (stat) { return stat.isFile(); }).length;
});
Как можно радоваться от промисов при виде этого стройного кода я не понимаю.
Причем даже в сложных ситуациях это все не разваливается а продолжает выглядеть понятно, как синхронный код. О промисах и callback'ах с async такого сказать нельзя.
+1
Как можно радоваться от промисов при виде этого стройного кода я не понимаю.
С точки зрения будущего стандарта языка (с которым лучше ознакомиться), да и текущего, идеологически более верно что-то в таком стиле:
var files = await promiseFs.readdir(dir)
, paths = files.map((file)=> path.join(dir, file))
, stats = await Promise.all(paths.map((path)=> promiseFs.stat(path)))
, countFiles = stats.filter((stat)=> stat.isFile()).length;
+3
Либо я так долго работал в таком стиле, либо все мои проекты и коллеги с кем я работаю придерживаются того, что js — это callbacks, async, promises, service bus и т.д. Но когда я вижу рекомендации писать js код в sync виде — выглядит это жутко.
0
Каких возможностей больше? На первый взгляд, при наличие генераторов async-await — просто лишняя сущность. Фактически одно и то же, только ключевые слова разные.
0
Например, несколько интерфейсов у async функции: она может возвращать thunk, promise или принимать обычный callback в традиционном стиле; await тоже принимает все варианты. Это, конечно, просто возможности библиотеки и аналог можно реализовать и в co, но на данный момент получается, что asyncawait лучше будет работать с существующим кодом.
Есть еще небольшая фича, которую, как мне кажется, с помощью генераторов сделать не получится: внутри async-функции любые другие async-функции можно вызывать без await. Т.е. для своего кода можно достичь результата, где весь код выглядит синхронным и await даже не упоминается, а если сделать аналог require('bluebird').promisifyAll(require('fs')) для async функций (т.е. async.asyncifyAll()), то и вообще весь код станет визуально синхронным.
Но… в будущем стандарте это вроде не предусмотрено, так что, наверное, надолго останется возможностью конкретной библиотеки.
Есть еще небольшая фича, которую, как мне кажется, с помощью генераторов сделать не получится: внутри async-функции любые другие async-функции можно вызывать без await. Т.е. для своего кода можно достичь результата, где весь код выглядит синхронным и await даже не упоминается, а если сделать аналог require('bluebird').promisifyAll(require('fs')) для async функций (т.е. async.asyncifyAll()), то и вообще весь код станет визуально синхронным.
Но… в будущем стандарте это вроде не предусмотрено, так что, наверное, надолго останется возможностью конкретной библиотеки.
0
Там промисы внутри
Basic Example
var async = require('asyncawait/async');
var await = require('asyncawait/await');
var Promise = require('bluebird');
var fs = Promise.promisifyAll(require('fs')); // adds Async() versions that return promises
var path = require('path');
var _ = require('lodash');
/** Returns the number of files in the given directory. */
var countFiles = async (function (dir) {
var files = await (fs.readdirAsync(dir));
var paths = _.map(files, function (file) { return path.join(dir, file); });
var stats = await (_.map(paths, function (path) { return fs.statAsync(path); })); // parallel!
return _.filter(stats, function (stat) { return stat.isFile(); }).length;
});
Basic Example
var async = require('asyncawait/async');
var await = require('asyncawait/await');
var Promise = require('bluebird');
var fs = Promise.promisifyAll(require('fs')); // adds Async() versions that return promises
var path = require('path');
var _ = require('lodash');
/** Returns the number of files in the given directory. */
var countFiles = async (function (dir) {
var files = await (fs.readdirAsync(dir));
var paths = _.map(files, function (file) { return path.join(dir, file); });
var stats = await (_.map(paths, function (path) { return fs.statAsync(path); })); // parallel!
return _.filter(stats, function (stat) { return stat.isFile(); }).length;
});
0
Генераторы это замечательно, но не панацея. Генераторы — возможность асинхронного ожидания результата функции, когда промисы — универсальный интерфейс асинхронных функций и способ обработки ошибок в них.
Promise поддерживается всеми современными браузерами, для старых достаточно простого полифила, когда генераторы требуют компиляции в лютый трэш, при взгляде на который седеешь.
async / await
, что грозятся попасть в ES7, базируется как раз на связке генераторов и промисов.Promise поддерживается всеми современными браузерами, для старых достаточно простого полифила, когда генераторы требуют компиляции в лютый трэш, при взгляде на который седеешь.
+2
ну, так может не стоит просто смотреть в результат?) главное, что работает
-2
А дебажить потом как, если не смотреть в результат?
+1
эмгх, sourcemaps же.
+1
Не только это главное.
0
Промисы хороши там, где идет цепочечная обработка данных
Правильно, поэтому надо любые вещи стараться сделать цепочкой.
getTokenInfoFor(token).then(function(tokenInfo){
return getUserInfo(tokenInfo.email).then(function(userInfo){ // подменяем результат выполнения первого промиса вторым
if (userInfo.tokenRevokeTime < tokenInfo.issueTime) throw errors.tokenRevoked; // выброс ошибки приведет к reject всех промисов ниже по цепи
return userInfo; // отдаем это как результат второго промиса
});
})
.then(function(userInfo){ ... })
.catch(function(rejection){ ... });
Вы, фактически, применили антипаттерн из статьи, потеряв ваш промис. Если добавятся еще какие-то действия, то их можно подключать в цепочку хоть до посинения и без дикой вложенности.
+1
Не вижу сильной разницы между моим и вашим кодом, вы просто заменили reject на throw, а resolve на return. Catch относительно дорогая операция, так что лучше ее избегать.
В реальном коде было четыре(!) уровня вложенности, и от них реально было никуда не деться. Я писал пример по памяти, так что…
В реальном коде было четыре(!) уровня вложенности, и от них реально было никуда не деться. Я писал пример по памяти, так что…
0
Приглядитесь, в моем коде ошибки промисов не потеряются, а в Вашем — они никак не будут обработаны. А так разница мала, да. Насчет уровней вложенности, готов поспорить, что их можно было бы вывернуть из вложенности в обычную цепочку, если правильно модифицировать результат Promise'ов.
0
Реализовать волокна на генераторах, безусловно, можно, но это использование инструмента не по назначению (костыль со множеством ограничений). Если есть возможность лучше использовать нативные волокна (node-fibers, например). С ними асинхронность не просачивается за пределы асинхронной функции:
Для себя я реализовал несколько хелперов:
$jin.async2sync — превращает асинхронную (в стиле nodejs) функцию в синхронную, которая останавливает текущее волокно до окончания своего исполнения
$jin.sync2async — наоборот, превращает синхронную в асинхронную (при необходимости заворачивает её в волокно)
Рекомендую почитать эту статью, где объясняется почему генераторы — это плохо, а волокна — хорошо: howtonode.org/generators-vs-fibers
Впрочем, генераторы, конечно, лучше чем обещания :-)
function getUserInfoByToken( token ) {
var tokenInfo = getTokenInfoFor(token);
var userInfo = getUserInfoByMail(tokenInfo.email);
if (userInfo.tokenRevokeTime > tokenInfo.issueTime)
throw e;
return userInfo;
}
Для себя я реализовал несколько хелперов:
$jin.async2sync — превращает асинхронную (в стиле nodejs) функцию в синхронную, которая останавливает текущее волокно до окончания своего исполнения
$jin.sync2async — наоборот, превращает синхронную в асинхронную (при необходимости заворачивает её в волокно)
Рекомендую почитать эту статью, где объясняется почему генераторы — это плохо, а волокна — хорошо: howtonode.org/generators-vs-fibers
Впрочем, генераторы, конечно, лучше чем обещания :-)
0
Можно попробовать так:
Получается только один уровень вложенности и, видно, что от чего зависит
var tokenInfo = getTokenInfoFor(token),
userInfo = tokenInfo.then(function(tokenInfo){
return getUserInfo(tokenInfo.email)
});
Q.all(tokenInfo, userInfo).spread(function(tokenInfo, userInfo){
if (userInfo.tokenRevokeTime > tokenInfo.issueTime)
return Q.resolve(userInfo);
else
return Q.reject(errors.tokenRevoked);
});
Получается только один уровень вложенности и, видно, что от чего зависит
0
Попробуйте разрулить нетривиальный поток выполнения на callbacks, желательно с обработкой ошибок, если вы не напишете свою имплементацию Promises, код, скорее всего, будет невозможно читать и понимать
Все верно, промисы — навороченные колбеки. Но, как выше писали, это не панацея, есть и другие подходы. Я о событиях.
0
События для другого. Promise одноразовый по определению, события — сущность многоразовая. Не стоит мешать две разные концепции, их следует использовать строго там, где это дает наибольший профит.
0
Какая принципиальная разница, сколько раз вызвано событие? Да и сама «одноразовость» ограничена лишь спецификацией, кем-то придуманной, потому что так хорошо ложилось в концепцию. Я же говорю о том, что это не единственно возможная концепция.
Для меня лично одноразовость это минус. Именно по этому на каждый «then» создается новый объект (+несколько областей видимости). Это так же порождает и недостатки, указанные в посте. EventEmitter никогда не проглотит ошибку. И ещё куча минусов есть.
Для меня лично одноразовость это минус. Именно по этому на каждый «then» создается новый объект (+несколько областей видимости). Это так же порождает и недостатки, указанные в посте. EventEmitter никогда не проглотит ошибку. И ещё куча минусов есть.
-2
Promise.all() не имеет отношения к параллелизму. Тут речь лишь о том, что порядок вычисления не важен (асинхронности в смысле). С параллелизмом в js вообще все сложно.
В jQuery реализация promise A (без плюса), от чего на практике одни минусы. Сталкивался с тем, что объекту Deferred можно сделать reject(), после чего resolve() и обещание внезапно становится выполненным.
В jQuery реализация promise A (без плюса), от чего на практике одни минусы. Сталкивался с тем, что объекту Deferred можно сделать reject(), после чего resolve() и обещание внезапно становится выполненным.
0
Скажите, пожалуйста, правильно ли я понимаю, что вызвать разрешение обещания можно только внутри функции, переданной аргументом в конструктор? То есть нельзя так же, как в jQuery, создать deferred-объект и потом где-нибудь, в произвольной точке кода, вызвать его разрешение?
0
Никто конечно не мешает вытащить функции разрешения наружу. Но это как раз и является лично для меня раздражающим фактором в конструкции Q.defer(), т.к. в случае с Revealing Constructor Pattern (т.е. как реализован Promise), разрешение возможно только внутри, что в большинстве случаев является необходимым и достаточным. Затрудняюсь придумать сценарий, когда Promise создается в одном месте, а резолвится в другом.
0
Ну вот у нас часто применяется примерно такой подход:
И как это переписать на промисы? Как-то, наверное, можно, но не похоже, чтобы это было удобно.
let deferred1=$.deferred(), deferred2=$.deferred();
function callbackFn1(arr){
…//обработка ответа № 1 сервера
deferred1.resolve(arr);
}
function callbackFn2(arr){
…//обработка ответа № 2 сервера
deferred2.resolve(arr);
}
function callbackFn3(arr1, arr2){
…//обработка, которой нужно оба ответа сервера
}
$.deferred().when(deferred1, deferred2).then(callbackFn3);
//в какой-то момент инициируется запрос № 1
$.request(AJAX-запрос_1, callbackFn1);
//а в какой-то другой момент независимо инициируется запрос № 2
$.request(AJAX-запрос_2, callbackFn2);
И как это переписать на промисы? Как-то, наверное, можно, но не похоже, чтобы это было удобно.
0
У вас спагетти получилось :-)
Я бы сделал так:
Я бы сделал так:
// первый ленивый загрузчик данных
var first = $jin.atom.prop({
pull: atom => {
$.request(AJAX-запрос_1, arr => atom.push( arr ) )
throw new $jin.atom.wait( 'Request 1' )
}
})
// второй ленивый загрузчик данных
var second = $jin.atom.prop({
pull: atom => {
$.request(AJAX-запрос_2, arr => atom.push( arr ) )
throw new $jin.atom.wait( 'Request 2' )
}
})
// ленивый запускатель обеих реквестов параллельно и рендерер результата в документ
var result = $jin.atom.prop({
pull: atom => {
var data = $jin.atom.get([ first , second ])
return data[0].concat( data[1] )
},
notify: ( atom , next ) => document.body.innerHTML = next.join( '|' )
})
// запускаем синхронизацию сервера с клиентом
result.pull()
// обновляем первую пачку данных через 10 секунд
setTimeout( () => first.update() , 10000 )
0
Когда ж у меня найдется время Ваши атомы потестировать…
У меня есть просьба — можно статью о том, как использовать атомы в приложении на React, а еще лучше — с учетом Flux. По-моему, отлично бы вписалось. Например чтоб можно было сравнить с github.com/facebook/flux/tree/master/examples/flux-chat.
У меня есть просьба — можно статью о том, как использовать атомы в приложении на React, а еще лучше — с учетом Flux. По-моему, отлично бы вписалось. Например чтоб можно было сравнить с github.com/facebook/flux/tree/master/examples/flux-chat.
0
в функции timeout не хватает promise.catch(reject); или я не прав?
+1
Он там совершенно ни к чему, ловить мы внутри функции ничего не планируем.
0
timeout(Promise.reject(), 1e4).catch(function(){
console.log('Да ладно? Мало того, что зря ждём таймаут, так ещё и unhandled rejection %)');
});
+1
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
ECMAScript 6 Promises