Рекурсия промисов. Все правильно?
Всем привет.
Прошу проверить код. Было так: <html> <head> <script src="https://code.jquery.com/jquery-1.12.4.js"></script> <script> 'use strict' var obj = {count: 0, maxCount: 5}; while (isNeedContinue(obj)) { doAnything(); console.log("Что-то еще..."); } function doAnything() { obj.count++; console.log("doAnything", obj.count); } function isNeedContinue(obj) { console.log("isNeedContinue", obj, obj.count < obj.maxCount); return obj.count < obj.maxCount } </script> </head> </html> Функция doAnything стала асинхронной. Переделал так: <html> <head> <script src="https://code.jquery.com/jquery-1.12.4.js"></script> <script> 'use strict' var obj = {count: 0, maxCount: 5}; *!*$.when((function recursive() {*/!* *!*if*/!* (isNeedContinue(obj)) { *!*return*/!* doAnything()*!*.then(function () {*/!* console.log("Что-то еще..."); *!*return recursive()*/!* }) } })()) .done(function (res) { console.log("Done", res); }) .fail(function (err) { console.log("Fail", err); }); function doAnything() { *!*var deferred = $.Deferred();*/!* *!*setTimeout(function () {*/!* obj.count++; console.log("doAnything", obj.count); //if (obj.count==3) deferred.reject(new Error("err!")); *!*deferred.resolve(obj);*/!* *!*}, 1000)*/!* *!*return deferred.promise()*/!* } function isNeedContinue(obj) { console.log("isNeedContinue", obj, obj.count < obj.maxCount); return obj.count < obj.maxCount } </script> </head> </html> Все ли путем? Технически, все вроде работает. Но вдруг я что-то упустил? Осваиваю JS, буду благодарен за любую конструктивную критику. |
дефереды безнадежно устарели. Забудь о них.
лучше юзать нативные промисы. https://jsfiddle.net/jgk0v70f/ |
Цитата:
За пример спасибо. :) |
Цитата:
добавь полифилл, и всё заработает. не нужен тут никакой бабель (к счастью). |
Alexandroppolus,
а не подскажете "правильный" полифилл? Гугл выдает много всего, в т.ч. разные самописные. Есть какой-то стандарт де факто? Вот этот https://github.com/stefanpenner/es6-promise правильный? |
https://github.com/petkaantonov/bluebird вроде самый быстрый. Петька Антонов (автор) повёрнут на быстродействии )
|
Alexandroppolus,
Спасибо, попробую. Вот этот тоже вроде неплох https://github.com/lahmatiy/es6-promise-polyfill. Реализует только стандартный функционал, просто подключается, весит 2,6кБ. |
Код, который выполняется после цикла, хочется вынести из функции.
Так будет правильно? Можно ли упростить выделенный фрагмент? Спасибо. 'use strict' var obj = {count: 0, maxCount: 5}; function doAnything() { return new Promise(function(resolve, reject) { setTimeout(function () { obj.count++; console.log("doAnything", obj.count); var testError = false; if (testError && obj.count==3) { console.log("doAnything Error!"); reject(new Error("err!")); } resolve(obj); }, 500) }) } function isNeedContinue(obj) { console.log("isNeedContinue", obj, obj.count < obj.maxCount); return obj.count < obj.maxCount } *!* (new Promise(function (resolve, reject) { (function _repeat() { if (isNeedContinue(obj)) { doAnything().then( function () { console.log("Что-то в цикле..."); _repeat(); }, function (error) { console.log("Ошибка!"); reject(error); }) } else { console.log("Конец цикла"); resolve(obj); } })() })) */!* .then(function (res) { console.log("Что-то после цикла...", res); }) .catch(function (err) { console.log("Fail", err); throw err; }); |
Вынес while в отдельную функцию.
Есть какие-нибудь противопоказания? Основной код читаемый получился? 'use strict' var obj = {count: 0, maxCount: 5}; function isNeedContinue(obj) { console.log("isNeedContinue", obj, obj.count < obj.maxCount); return obj.count < obj.maxCount } function doAnything() { return new Promise(function(resolve, reject) { setTimeout(function() { obj.count++; console.log("doAnything", obj.count); var testError = false; if (testError && obj.count==3) { console.log("doAnything Error!"); reject(new Error("err!")); } resolve(obj); }, 500) }) } function whilePromise(condition, promise) { return new Promise(function(resolve, reject) { var _lastResult; (function _nextIteration() { if (condition()) { //console.log("whilePromise. Next iteration."); promise().then(function(result) { _lastResult = result; _nextIteration(); }, reject); } else { //console.log("whilePromise. End"); resolve(_lastResult); _lastResult = null; } })() }) } // Основной код whilePromise( isNeedContinue.bind(null, obj), function() { return doAnything() .then(function() { console.log("Что-то еще в цикле..."); return Promise.resolve(obj); }, Promise.reject.bind(Promise)) } ) .then(function(res) { console.log("Что-то после цикла...", res); }) .catch(function(err) { console.log("Fail"); throw err; }); |
Цитата:
У рекурсии есть прямой и обратный ход, глубина. Сейчас специльно погуглил определении рекурсии, и там прямо так и написано, что рекурсия -- это вызов функции (процедуры) самой себя. Что ж, могу добавить -- "видимо, только в контексте синхронного исполнения". |
Цитата:
|
Функция вызывает саму себя, значит это рекурсия.
Стек вызовов из-за асинхронности не растет. Назовите это - асинхронная рекурсия. :) |
Меня другое волнует.
Синхронный код был простой и понятный. Я хочу: 1. Убедиться, что эквивалентный асинхронный код правильный. По тестам это так, но вдруг я что-то не учел. 2. Чтобы асинхронный код был понятен. Не верю, что я решаю уникальную задачу. Возможно уже есть общепринятые паттерны на этот случай. |
И в последнем варианте мне не нравится один момент.
В теле "цикла" приходится добавлять return в двух местах. Кажется, не очевидно, для чего это делается. Меня, как JSера с 2-хмесячным стажем, еще месяц назад это ввело бы в ступор. :blink: whilePromise( isNeedContinue.bind(null, obj), function() { *!*return*/!* doAnything() .then(function() { console.log("Что-то еще в цикле..."); *!*return*/!* Promise.resolve(obj); }, Promise.reject.bind(Promise)) } ) Или я зря страдаю? |
SergeyERjs,
зря страдаешь. "return в двух местах" - это когда в пределах одной функции. Бытует мнение, что это антипаттерн. Хотя нельзя сказать наверняка. (с) У тебя ретурны в разных функциях, только не обязательно делать return Promise.resolve(obj);, достаточно просто return obj; Promise.reject.bind(Promise) вообще ни к селу ни к городу. Как ты это придумал, ума не приложу. Наверняка читал говностатейки по программированию на каком-нибудь говносайтишке. https://learn.javascript.ru/ - здесь и только здесь надо начинать постигать. Правильный вариант: whilePromise( isNeedContinue.bind(null, obj), function() { return doAnything() .then(function(data) { console.log("Что-то еще в цикле..."); return data; }); } ) Тут просто передаем то значение data, которое получили в промисе doAnything(). А вот если надо будет воткнуть что-то асинхронное между итерациями, тогда возвращаем промис. |
Цитата:
function foo() { setTimeout(foo, 1e3) } foo() ? :) |
Цитата:
Alexandroppolus, спасибо! :) Цитата:
Мои основные источники: https://learn.javascript.ru/ + https://javascript.ru/manual + https://developer.mozilla.org/ + http://api.jquery.com/ и т.п. |
Цитата:
И да, это похоже на хвостовую рекурсию (после того, как компилятор её оптимизирует, конечно). Только там итерации следуют непрерывно друг за другом, а здесь раскиданы по таскам в event loop. В обоих случаях не нагромождается стек, нет обратного хода. |
Часовой пояс GMT +3, время: 02:28. |