Javascript-форум (https://javascript.ru/forum/)
-   Общие вопросы Javascript (https://javascript.ru/forum/misc/)
-   -   Рекурсия промисов. Все правильно? (https://javascript.ru/forum/misc/69674-rekursiya-promisov-vse-pravilno.html)

SergeyERjs 11.07.2017 13:23

Рекурсия промисов. Все правильно?
 
Всем привет.
Прошу проверить код.

Было так:
<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, буду благодарен за любую конструктивную критику.

Alexandroppolus 11.07.2017 16:58

дефереды безнадежно устарели. Забудь о них.

лучше юзать нативные промисы. https://jsfiddle.net/jgk0v70f/

SergeyERjs 11.07.2017 17:09

Цитата:

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

За пример спасибо. :)

Alexandroppolus 11.07.2017 18:25

Цитата:

Сообщение от SergeyERjs
В IE не работает, а это критично.

полифилл же есть.
добавь полифилл, и всё заработает. не нужен тут никакой бабель (к счастью).

SergeyERjs 12.07.2017 12:09

Alexandroppolus,
а не подскажете "правильный" полифилл?
Гугл выдает много всего, в т.ч. разные самописные. Есть какой-то стандарт де факто?

Вот этот https://github.com/stefanpenner/es6-promise правильный?

Alexandroppolus 12.07.2017 13:49

https://github.com/petkaantonov/bluebird вроде самый быстрый. Петька Антонов (автор) повёрнут на быстродействии )

SergeyERjs 12.07.2017 14:32

Alexandroppolus,
Спасибо, попробую.

Вот этот тоже вроде неплох https://github.com/lahmatiy/es6-promise-polyfill. Реализует только стандартный функционал, просто подключается, весит 2,6кБ.

SergeyERjs 12.07.2017 20:39

Код, который выполняется после цикла, хочется вынести из функции.
Так будет правильно? Можно ли упростить выделенный фрагмент?
Спасибо.
'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;
	});

SergeyERjs 13.07.2017 00:55

Вынес 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;
	});

nerv_ 13.07.2017 09:59

Цитата:

Сообщение от SergeyERjs
Рекурсия промисов.

Прозвучит странно, но я бы не называл это рекурсией. Скорее просто отложенный вызов функции самой себя (привет асинхронность).

У рекурсии есть прямой и обратный ход, глубина.

Сейчас специльно погуглил определении рекурсии, и там прямо так и написано, что рекурсия -- это вызов функции (процедуры) самой себя.
Что ж, могу добавить -- "видимо, только в контексте синхронного исполнения".

Alexandroppolus 13.07.2017 11:42

Цитата:

Сообщение от nerv_
Прозвучит странно, но я бы не называл это рекурсией. Скорее просто отложенный вызов функции самой себя (привет асинхронность).

У рекурсии есть прямой и обратный ход, глубина.

я думаю, тут некая разновидность хвостовой рекурсии )

SergeyERjs 13.07.2017 13:02

Функция вызывает саму себя, значит это рекурсия.
Стек вызовов из-за асинхронности не растет.
Назовите это - асинхронная рекурсия. :)

SergeyERjs 13.07.2017 13:10

Меня другое волнует.
Синхронный код был простой и понятный.
Я хочу:
1. Убедиться, что эквивалентный асинхронный код правильный.
По тестам это так, но вдруг я что-то не учел.
2. Чтобы асинхронный код был понятен.
Не верю, что я решаю уникальную задачу. Возможно уже есть общепринятые паттерны на этот случай.

SergeyERjs 13.07.2017 13:23

И в последнем варианте мне не нравится один момент.
В теле "цикла" приходится добавлять return в двух местах. Кажется, не очевидно, для чего это делается. Меня, как JSера с 2-хмесячным стажем, еще месяц назад это ввело бы в ступор. :blink:
whilePromise(
	isNeedContinue.bind(null, obj),
	function() {
		*!*return*/!* doAnything()
			.then(function() {
				console.log("Что-то еще в цикле...");
				*!*return*/!* Promise.resolve(obj);
			}, Promise.reject.bind(Promise))
	}
)

Или я зря страдаю?

Alexandroppolus 13.07.2017 14:00

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(). А вот если надо будет воткнуть что-то асинхронное между итерациями, тогда возвращаем промис.

nerv_ 13.07.2017 14:07

Цитата:

Сообщение от Alexandroppolus
я думаю, тут некая разновидность хвостовой рекурсии )

а как бы вы назвали это:
function foo() {
  setTimeout(foo, 1e3)
}
foo()

? :)

SergeyERjs 13.07.2017 14:18

Цитата:

.then(function(data)
Точно! Вот я же чую, чо-то не так. Проще должно быть.
Alexandroppolus, спасибо! :)

Цитата:

Наверняка читал говностатейки...
А вот это лишнее замечание.

Мои основные источники:
https://learn.javascript.ru/
+ https://javascript.ru/manual
+ https://developer.mozilla.org/
+ http://api.jquery.com/ и т.п.

Alexandroppolus 13.07.2017 15:27

Цитата:

Сообщение от nerv_ (Сообщение 458393)
а как бы вы назвали это:
function foo() {
  setTimeout(foo, 1e3)
}
foo()

? :)

ну по сути это в кратком виде тема сабжа.

И да, это похоже на хвостовую рекурсию (после того, как компилятор её оптимизирует, конечно). Только там итерации следуют непрерывно друг за другом, а здесь раскиданы по таскам в event loop. В обоих случаях не нагромождается стек, нет обратного хода.


Часовой пояс GMT +3, время: 02:28.