Сучасна диджитал-освіта для дітей — безоплатне заняття в GoITeens ×
Mazda CX 5
×

JavaScript Promises. Chaining

Підписуйтеся на Telegram-канал «DOU #tech», щоб не пропустити нові технічні статті

Людям, хорошо знающим JS, вряд ли будет интересно, но я был бы благодарен за «review», ибо на js я автоматизирую не больше полугода и могу что-то упустить...
Тех, кому интересна данная тема — прошу вниз )

У нас на проекте было некое недопонимание в плане того, что иногда инструкция в зене не ждёт выполнения предыдущего зена. Частный случай:

promise
.then(browser.wait(time))
В этом случае мы не ждём в том месте где хотели бы ждать. Почему так происходит... Немного теории: JS — синхронный! У него есть Call Stack и Callback Queue. В Callback Queue падает весь «асинхронный» код, т.е. колбеки или промисы (те же колбеки, только в профиль). Всё остальное идёт в Call Stack. Call Stack всегда выполняется первым. Когда там пусто — выполняются задачи из Callback Queue.

Теперь рассмотрим другой пример:

var promise = new Promise(function(resolve, reject) {
	resolve(1);
});

function logger(message) {
	console.log(message);
}

promise
.then(logger)
.then(logger(2))
.then(() => logger(3));
В аутпуте будет сначала 2, потом 1 и 3. Почему так происходит:
then — функция, которая опционально принимает 2 колбека — 1-ый обрабатывает резолв, 2-ой — реджект. В нашем примере мы не обрабатываем реджект и работаем только с резолвом. Итак,

1-ый then: в него мы передаём колбек, который выполнится с результатом выполнения предыдущего действия (promise). Т.е.

.then(logger) === .then(whateverPromiseReturns => logger(whateverPromiseReturns))
Т.е. logger(1), который пойдёт в Callback Queue

2-ой then: здесь мы вместо колбека просто вызываем функцию. Если мы хотим чейнить, то так делать не надо, ибо код из зена пойдёт в Call Stack. Это как раз наш Частный случай. Т.к. это не колбек, то logger(2) пойдёт в Call Stack.

3-ий then: пишем колбек прямо на месте и внутри него выполняем logger(3), который соответственно пойдёт в Callback Queue.

Теперь всё логично, в очереди у нас logger(2), а в Callback Queue: logger(1) и logger(3). Call Stack выполняется первым, поэтому мы видим 2 в начале аутпута, после выполняется Callback Queue, который пишет 1 и 3.

UPD3:
JS даёт возможность вызывать в зене функцию, а не передавать ей колбек. Это сделано для того, чтобы вызвать функцию, которая вернёт функцию с замкнутыми переменными или контекстом. Но нужно быть осторожными, чтоб не получилось так:

var promise = new Promise(function(resolve, reject) {
	reject();
});
promise
.then(console.log(1), () => console.log(2), console.log(3))
Вернёт 1 3 2...

Прошу в комменты )

UPD: Хороший пример из комментов, который более наглядно показывает проблему:

const promise = new Promise((resolve, reject) => resolve(1));
const logger = (mes) => {
	console.log("Side effect: ", mes);
	return (arg) => { console.log("Resolved: ", arg); }
} 
promise
	.then(logger(1))
	.then(logger(2))
	.then(logger(3))

UPD2: Очень классное видео про event loop, call stack и callback queue. Плюс описана разница между колбеками в array helpers и async callback.
latentflip.com/loupe
Спасибо, Alex Dubovyk!

👍ПодобаєтьсяСподобалось0
До обраногоВ обраному0
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Цікаво, ніколи не передавав із catch значення далі. Так увесь ваш приклад написаний саме для цього?

Как по мне это большой огрех JS и он должен был бы кидать ошибку, если не видит колбека в зене.
С чего вдруг, если оба колбека опциональные?

А зачем они оба опциональные, кроме того, что так в стандарте написано?

Проблема в том, что когда писали стандарт, не спросили мнение у Gennadii Mishchevskii

А на вопрос ответить? Или только тролить?

шоб можно было передать только первый или только второй(null, some_callback).
а проверять «либо то, либо то — обязательно» технически возможно. но в реальности никогда не встречал сообщение "either A or B must be ..."(применительно хоть к чему)

Это понятно, но я ж могу передать 3 аргумента, могу 2, которые не колбеки. И отсюда вылазят проблемы.

(null, some_callback)
Вы никогда не видели (null, some_callback())? Я видел. То, что можно использовать только один обработчик — это классно, но то, что можно втулить сколько хочешь и вообще не колбеков — мне не понятно. И никто не хочет отвечать зачем это сделано и где можно использовать.
Вы никогда не видели (null, some_callback())? Я видел.
Вы уверены, что знаете, зачем нужен второй аргумент в then?

Это техничный уход от вопроса?
Зен опционально принимает 2 колбека. 1-ый обрабатывает resolve, 2-ой — reject. Но оба они — колбеки. Я говорю про ошибку, когда вместо колбека идёт вызов функции (null, some_callback()) и some_callback() не возвращает функцию, т.е. зен получает null на resolve и не получает колбека на reject, в итоге получается не то, что планировалось. И никаких варнингов и иксепшинов. Мой вопрос всем и лично вам: есть ли случаи, когда нужно передать в зен не колбек будь-то на resolve или reject, а вызов функции, который пойдёт в Call Stack вместо Callback Queue? Я ответа на этот вопрос не знаю, посему и говорю, что мне хотелось бы этот варгнинг, а лушче иксепшн.

Мой вопрос всем и лично вам: есть ли случаи, когда нужно передать в зен не колбек будь-то на resolve или reject, а вызов функции
да запросто же:
.then($.proxy(obj.method, obj))
ну, или какой-то obj.method.bind(obj)
мне хотелось бы этот варгнинг
всё. очень. просто.
Перекрываете Promise.prototype.then своим кастомным вариантом с дополнительной проверкой.
А я вот, предпочту не ломать приложение(exception? серьезно?), а буду полагаться на тестирование и дебаг. Потому что даже ругань на отсутствующий параметр не застрахует меня от ошибки в логике кода.
да запросто же: .then($.proxy(obj.method, obj))
Я совсем не фронт-енд. Можете пояснить, что эта штука делает? И, если она не возвращает функцию, то пойдёт в кол стек и зачем тогда это в чейне?
(exception? серьезно?)
Конечно серьёзно ) Опять таки, может, на фронте оно было бы лишним, но в тестах давайте представим тот же первоначальный пример. Человек сделал ошибку и поставил вейтер (по незнанию), который не будет ждать там, где ему надо. Тесты валятся с периодичностью раз в 5-10 прогонов, т.к. то, что должен был ждать вейтер в основном успевает выполнится и без него. Я понимаю, что написал не разобравшись — сам дурак, но все с чего-то начинали и я, пришедший в js из питона, всё ещё очень люблю, когда интерпритатор говорит мне сразу когда где и какая ошибка была с чётко указанным местом в коде.
Можете пояснить, что эта штука делает?
фиксирует this. полифилл для современного Function.prototype.bind
Тесты валятся с периодичностью раз в 5-10 прогонов, т.к. то, что должен был ждать вейтер в основном успевает выполнится и без него
это очень неприятно, согласен.
но это ошибка логическая, я все еще не понимаю, зачем (желать) ограничивать возможности платформы из-за того, что кто-то не (совсем) может в промисы, а процесс с код-ревью не выстроен.

Я не видел и пока не вижу применения этим возможностям, а проблемы вижу. Я признаю, что вполне вероятно, что я сейчас так думаю, ибо не встречал необходимости использования этих возможностей.

Я не видел и пока не вижу применения этим возможностям
пришло в голову предупредить еще об одном подводном камне(на тот случай, если вы .bind() раньше не встречали). Если передавать метод в качестве аргумента, то при вызове он будет выполнен на глобальном this.
var a = {
 b: 20,
 c: function () {console.log(this.b);}
};
....
....
.then(a.c); // выдаст undefined, а не "20", так как метод выполнится вне контекста соответствующего объекта

.then(a.c.bind(a)) // выдаст 20

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

Это как раз понятно и занятно ) Я бы отнёс это к плюсам

Вы никогда не видели (null, some_callback())? Я видел.
я даже писал подобное. с передачей параметра в функцию, которая возвращает фунцкию. Потом, правда, отрефакторил на подмену this через .bind, так как читаемость страдала.

Я, конечно, не фронт-ендщик? но как по мне ф-я, которая возвращает ф-ю, чтоб потом сделать кол первой ф-ии в зене — это прыжок с переподвыподвертом. Может, я, конечно, и ошибаюсь.

ф-я, которая возвращает ф-ю
Это не
прыжок с переподвыподвертом.
Это называется функция высшего порядка. И в функциональных языках, таких как JS, используется сплошь и рядом.

Ок, тогда последний вопрос ) Вы так в продакшн коде делали? В зене возов ф-ии, которая возвращает ф-ию и по-другому (проще и очевидней) сделать ну никак нельзя?

В смысле? Конечно делал. Можно и на месте написать функцию конечно. Но если эта функция повторяется 20 раз потом в коде, то зачем?

Рассматривайте все, кроме функций в .then как SideEffect, о котором я писал ниже. Может быть вы в начале вашего resolve процесса хотите завязать какой-то observable, или сохранить timestamp, а впихивать эту ерунду в колбэк первого .then нет никакого смысла. Зачем вас лишать такой возможности? Код это никак не ломает, хоть и делает его менее понятным.

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

Если у вас функция возвращает функцию, то сделать это нужно только для того, чтоб замкнуть какие-то переменные.
Не обязательно, но так(не всегда правда переменные, можно и контекст, как с .bind) это используют чаще всего. Это просто возможность языка, которой можно пользоваться как сами пожелаете. Он так написан, это его структура. Почитайте, например, про каррирование и зачем оно надо.

В любом случае на вход в then() у вас будет функция, а не инструкция. И не важно, откуда она прилетела, с другой функции или нет. А передавать инструкции можно просто потому, что такую возможность вам дают. Поводов ее не давать я лично не вижу. Можно натупить и сделать вызов вместо передачи? Да так можно где угодно натупить и не только в promise. Так что теперь, всю консоль warninga-ми засыпать? =)

Спасибо, почитаю.

Так что теперь, всю консоль warninga-ми засыпать? =)
А это больная тема ) Мне лично и всем, кто у нас пишет бекенд и иногда нужно сделать что-то на js, очень не хватает варнингов и иксепшинов в консоли. Я пишу на python и js так вот строгая типизация питона радует. )
Да, и спасибо, что ответили на мои вопросы. Теперь я спокоен )
Мне лично и всем, кто у нас пишет бекенд и иногда нужно сделать что-то на js
Пусть ваши коллеги разок прочитают про First Class Functions и все будет ok. Тут дело не в отсутствии лишних предупреждений.

Тут сложность не только в этом, а больше в слабой типизации. На то, конечно, есть typeScript, но к сожалению.

очень не хватает варнингов и иксепшинов в консоли.
да потому что у джс такая философия — лучше сделать хрень, но не умереть аварийным завершением посередине скрипта. Так сложилось исторически, привыкай

и вообще, динамичность джс позволяет легко и непринужденно допиливать костыли. Вот ты пишешь на питоне, значит знаком с декораторами. Напиши декоратор для зена, который будет умирать, если что не так:)

var veryDangerousThen = Promise.prototype.then;
Promise.prototype.then = function (res, err) {
  Array.prototype.foreach.call(arguments, function (arg) {
    if (typeof arg !== "function" && typeof arg !== "null") {
      throw new Error("Pizdets!");
    }
  });
  return veryDangerousThen.call(this, res, err);
};
(не знаю, правильно ли написал, на джс особо не кодю)

JS не динамичный, иначе вы бы не могли в первой строчке сохранить статичное значение, которое потом поменяли дальше в коде.

Мы тут ниже чуток дискутировали про «ignored» аргументы. Так вот если у нас на вход не функция, то ваш arguments будет пустым и код завалится. Вообще наверно можно подобную обертку написать, но лучше не надо)

JS не динамичный, иначе вы бы не могли в первой строчке сохранить статичное значение, которое потом поменяли дальше в коде.
честно говоря ничего не понял, но это не важно
Так вот если у нас на вход не функция, то ваш arguments будет пустым и код завалится.
ничего подобного. arguments будет пустым только, если в функцию не передать вообще ничего. Да и foreach от прохождения по пустому массиву еще никогда не заваливался
можно подобную обертку написать, но лучше не надо)
разумеется, что не надо. Но раз уж ТС так хочет больше ексепшенов...
Да и foreach от прохождения по пустому массиву
ну, Антон писал про undefined. И тогда код таки валится:
TypeError: can’t convert undefined to object
Но это мелочи, плюс одна проверка или даже arguments || [] и всё

Не, похоже там все ok, arguments это всегда объект, не может быть undefined. Код валится потому, что нет функции foreach, а есть только forEach. Тогда выходит, что действительно аргументы кушаются.
Чуток переписал:

var veryDangerousThen = Promise.prototype.then;
Promise.prototype.then = function (res, err) {
	[].forEach.call(arguments, (arg) => { 
		if (typeof arg !== "function") 
			console.warn(`arg ${arg} is not a function`);
	});
  return veryDangerousThen.call(this, res, err);
};
Вроде даже адекватно работает)

ты зря убрал null из допустимых типов, тепер не вызвать then с одним reject без resolve (.then(null, onReject)). Задумка была в том, что случайно поймать udefined в джс очень легко, а null — сложнее, соответственно мы можем предположить, что null вкинули намеренно

тепер не вызвать then с одним reject
warn все равно функцию не сломает и спокойно вызовет. Если кидаем на «не функцию», то почему бы не кинуть на null. Можно и перепроверить конечно.
тепер не вызвать then с одним reject без resolve (.then(null, onReject))
.catch() - даже красивше.
.catch() - даже красивше.
Как пожелаете. Я бы в любом случае за такое переопределение палец отрезал =)
Я бы в любом случае за такое переопределение палец отрезал =)
когда-то мы юзали какой-то слекоподобный мессенджер (вроде glip но не помню точно), и наткнулись на то, что его авторы в веб-клиенте переопределили что-то из built-in objects, связанных с датой/временем. Офигели мы тогда знатно

Согласен, такого делать мы не будем )

Не, похоже там все ok, arguments это всегда объект
забавняк. у меня почему-то сломался console.log(выдавал undefined).
да, ваша правда, arguments в любом случае есть.
И никто не хочет отвечать зачем это сделано и где можно использовать.
ох. окей, заход на второй круг.

.then(shouldBeTransformed? transformationCallback: undefined) .then(....) .catch(...)

Вы что, издеваетесь? Где в этом примере вызов ф-ии? Если вы только потролить, то я пожалуй пройду мимо.

.then(shouldBeTransformed? transformationCallback(): undefined) .then(....) .catch(...)
и transformationCallback выполнится до всего чейна.

Ибо мой вопрос именно про те скобки, а не про то, что пишет Евгений.

угу. а

но то, что можно втулить сколько хочешь и вообще не колбеков — мне не понятно
мне померещилось. Конечно же, речь про скобки.

Ну так, Евгений вам про одно, а вы про свое.

Кстати, советую поискать на гитхабе — там есть разные примеры использования промисов, так сказать, в продакшене (например, внутри всяких angular, ui-router, react)

Ну так, Евгений вам про одно, а вы про свое.
Ну так меня ж интересует мой вопрос, а он про своё ) Вообщем, не понимаем мы друг-друга )
Спасибо, посмотрю на досуге.
Ну так меня ж интересует мой вопрос, а он про своё )
можно, вы его тут прямо для непонятливого меня напишете? вам не сложно?
потому что я в ваших сообщениях видел такие вопросы:
а) почему так?
б) это вообще может быть нужно?
в) когда это может быть нужно?
г) почему это не запретят?

но с ваших слов, вам нужно другое. видимо, я плох в чтении и понимании текста :(

Евгений, давайте я щас подытожу нашу с вами дискуссию и мы на этом закончим.

а) почему так?
Где вы нашли вопросительный знак в моём тексте после этих слов? Вы как медиа. У меня в тексте:
Вот код, он выполняет это, почему так — потому что потому. Здесь вопроса не было.
А где же он был? Внимание, здесь я тоже не прошу вас отвечать на вопрос, даже не смотря на то, что есть вопросительный знак.
А вопрос был: зачем нужно разрешать в зене вызов функции, а не кидадть варнинг на не колбек. На что Антон Мудренок однозначно и понятно ответил, что сделано это для того, чтобы вызвать функцию, которая вернёт функцию с замкнутыми переменными или контекстом.
Мой вопрос закрыт, соответствующий апдейт в топик добавлен, я спокоен, желаю и вам того же.
Спасибо за время и внимание и понимание )

Тут вам приводят пример, что в зависимости от определенного условия, мы можем включить в цепочку .then функцию, а можем ее и не включать, а просто продолжить цепочку вызовов и передать статичный undefined.

Да, это понятно, здесь вопроса нет и не было. Мы просто с Евгением на разных языках говорим и друг друга упорно не понимаем. Вы уже близки к ответу на все мои вопросы немножко выше.

Если Вы хотите чейнить зены, _возвращайте_ функцию. Если она promise, будет правильный чейн.

then и так возвращает промис, в кб переданный в зен можно возвращать все что душе угодно, или функцию или число. Просто если возвращаете !!result === false то промис упадет в pending и там и останется

Возвращает return, если по-хорошему. Then не then — это уже не важно. Забыл написать return — цепочка сломалась. Была у меня ссылка на статью, после которое промисы становятся понятны всем, но не могу найти

+ в карму, если найдёте )

Просто если возвращаете !!result === false то промис упадет в pending и там и останется
э? не упадет. или я не понял, к чему вы ведете.
var log = function (x) {console.log('value is ' + x); return x;}
new Promise(function (resolver) {
   setTimeout(function() {resolver(1);}, 2000);
}).
then(log).
then(function (x) {return 'aaa';}).
then(log).
then(function () {return;}).
then(log).
then(function () {return false;}).
then(log)

ну дык, вы ж вызываете resolver(1), молчу уже про setTimeout, он не обязателен.

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

но если не зарезолвить, то промис упадет в пендинг, это я и имел ввиду

ну дык, вы ж вызываете resolver(1)
эм. тогда я ваще не понял вашего сообщения про
Просто если возвращаете !!result === false то промис упадет в pending и там и останется
Ваше сообщение в теме про работу метода .then и цепочке комментариев про return в коллбеке в .then выглядело так, будто с этим какие-то нюансы.

Конструктор Promise принимает коллбек. Внутри которого будет (произвольная) логика, которая в какой-то момент зарезолвит или зареджектит промис. Если ни резолв, ни реджет никогда не вызываются — promise будет бесконечно висеть в pending. Логично ж?
UPD

молчу уже про setTimeout, он не обязателен
это для реализации хоть какой-то задержки. Можно и сразу вызвать.

Вообще, всё упирается в то, будет ли один из аргументов коллбека — resolver или rejector — вызван когда-нибудь или нет.
Если вы напишете
new Promise(function () {return 5;}) или любой другой return — промис всё равно будет бесконечно висеть в pending

полностью согласен, я это и имел ввиду да

Теперь всё логично, в очереди у нас logger(2), а в queue: logger(1) и logger(3).

Учитывая, что «queue» — это и есть «очередь», только по-английски, всё вообще нелогично, и только запутывает, кроме того, что некорректно по сути.

То, о чем здесь идет речь — это Call Stack и Callback Queue. Разница принципиальная. Нельзя просто так взять и назвать Call Stack — очередью.

Имхо, к ознакомлению до написания статей на эту тему:
latentflip.com/loupe

Как по мне это большой огрех JS и он должен был бы кидать ошибку, если не видит колбека в зене.

З коментарів автора теми:

Ибо у нас я такие ошибки видел не раз. Я, конечно, понимаю, что те, кто их допускает — сами виноваты, но всё же...Но замечание дельное, спасибо! Чуток подправил топик.

Я розумію що тестувальник не вирішує на чому буде писатись код, але TypeScript дуже чітко виявляє такі помилки. Щойно перевірив, пройшов лише останній приклад із «Side effect», але то вже точно не помилка (як говориться, це може бути фічою а не багом =).

Ще можете глянути мою стару тему, там я теж задавався подібними питаннями.

Я розумію що тестувальник не вирішує на чому буде писатись код
Я бы вообще писал на питоне всё (почти), кроме ангулярного юая.

Кто юзал на практике что будет удобней: старый Promise или новый(или хорошо забытый старый) RxJs?

При условии что фреймворк(как Ангуляр2) этот RxJs не поддерживает и все Observable, observer() и так далее надо имплементить самостоятельно?

В TypeScript 2.1 уже есть async/await , a Promise еще долго будут использоваться, не куда от них не деться ) Может кому то такой пример поможет понять Promise, а может еще больше запутает gist.github.com/...b9f9e85da8fb75ceadc91756a

При условии что фреймворк(как Ангуляр2) этот RxJs не поддерживает

Що мається на увазі під «не поддерживает»? Мабуть ви поверхнево прочитали ось цей абзац і вирішили, що RxJS Observable не підтримується. Насправді, Angular якраз у першу чергу підтримує RxJS Observable, а кому треба проміс, той може використати метод .toPromise().

я не очень четко выразился. Имелось в виду, что как раз нг2 поддерживает из коробки, а остальные, скажем тот же Реакт, нет.

хотел показать тем, кто не до конца понимает
Вы так можете еще больше запутать людей.

Начиная с примеров про стек и очередь. Стек это первый на вход — первый на выход. JS так не работает. Инструкция, которая прилетела последней на выполнение, не будет исполнена первой. Сравнивать поток асинхронных операций с очередью тоже неуместно, т.к. мы не всегда знаем заранее, когда операция будет выполнена. В очереди же первый на вход, последний на выход.

По поводу вашего примера, вы смешали людей/коней (асинхронное выполнение с side-effect функцией). Более понятно этот пример можно так переписать:
const promise = new Promise((resolve, reject) => resolve(1));
const logger = (mes) => {
	console.log("Side effect: ", mes);
	return (arg) => { console.log("Resolved: ", arg); }
} 
promise
	.then(logger(1))
	.then(logger(2))
	.then(logger(3))
И увидеть, что вначале отрабатываются side-effect-ы. На этом примере может стать действительно понятней, как что работает.

Спасибо за конструктивный ответ! Про стек и кью на курсах udemy смотрел. У меня тоже вот по этому моменту со стеком было недопонимание. Пример тоже классный, но как по мне — более сложный для новичков, хотя, может, я и ошибаюсь.

Разберись чем инструкция вызова функции отличается от передачи callback(ссылки на функцию). не разобрался с основами js

Я что-то неправильно написал или в чём суть претензии?

да, не правильно.
вам же Vicrot Miletsky ниже расписал подробно.

если с событиями так же делать, вообще работать не будет:
form.on(’submit’, logger(2))

then ожидает функцию в качестве аргумента. Или выражение, которое вернет функцию. logger(2) в вашем примере не вернет функцию(как ниже и указывали — отсутствие явного return в ней эквивалентно return; или return undefined;).

Мы о разных вещах говорим. Я с Виктором по этому поводу не спорил.
Суть топика в том, что я видел в коде, как в зене вместо колбека по ошибке вызывали функцию и оно работало так, как описано в стандарте, но не так как хотел тот, кто этот код написал.
Я выше привёл пример кода и описал как он работает. Если там есть ошибка, пожалуйста, процитируйте и скажите, что я там описал неправильно.

Если там есть ошибка, пожалуйста, процитируйте и скажите, что я там описал неправильно.
Я не совсем понимаю, что вы ожидаете от меня.
В том, что у вас происходит, нет ошибки — только не верное использование.
Ну, и про stack/queue вы как-то не рационально всё усложняете.
Достаточно было того, что выражение logger(2) сразу же вычислится и вернется его результат(а в отсутствие явного return — undefined) и выражение эквивалентно then(undefined).
например, [1,2,3].map(logger(2)) тоже не отработает. Безо всяких queue/stack разниц.
И это то, на чем набивают шишки новички. А еще обращение к this внутри setTimeout/event callback.
да, не правильно.
Вы написали, что не правильно, я и хотел уточнить, что именно.
В том, что у вас происходит, нет ошибки
А неправильное использование я и хотел показать и объяснить. А ещё хотел бы, чтоб язык предупреждал о неправильном использовании, но это, видимо, особенности js. Я понимаю, как это работает и хотел показать тем, кто не до конца понимает и заодно убедиться, что сам понимаю правильно.
А про стек и кью — мне самому так больше понятно, когда я понимаю, как оно работает под капотом, а не просто помню, что что-то выполняется первым, а что-то вторым.

Async Await решают проблему ’мы не ждём в том месте где хотелось бы ждать’

Я не много работал с авейтом, но промисы мне нравятся больше.

async/await это по сути обертка над промисами позволяющая писать псевдо-синхронный код, промисы никуда не деваются на самом деле. Но и это не решает вашу проблему, т.к. можно написать например так:

await promise.then(logger(2))

и как и прежде

logger(2) пойдёт в stack

и

мы не ждём в том месте где хотели бы ждать

Некоторые материалы которые помогут лучше вникнуть в тему async/await
habrahabr.ru/post/282477
www.youtube.com/watch?v=ocaV1zZZcAs
ponyfoo.com/...ng-javascript-async-await

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

Все очень просто,

logger(2) пойдёт в stack
и при этом вернет undefined, который передастся аргументом в then()

Смотрим в стандарт (пункт 2.2): promisesaplus.com

A promise’s then method accepts two arguments:

promise.then(onFulfilled, onRejected)

Both onFulfilled and onRejected are optional arguments:

If onFulfilled is not a function, it must be ignored.
If onRejected is not a function, it must be ignored.

Ключевое: If onFulfilled is not a function, it must be ignored.

Все работает так как и должно.

и при этом вернет undefined, который передастся аргументом в then()
с этим я не спорил )
Все работает так как и должно.
по стандарту. Но всё же я считаю, что ворнинг был бы не лишним. Ибо у нас я такие ошибки видел не раз. Я, конечно, понимаю, что те, кто их допускает — сами виноваты, но всё же...
Но замечание дельное, спасибо! Чуток подправил топик.
и при этом вернет undefined, который передастся аргументом в then()
Не передастся
(new Promise(resolve => resolve("resolved")))
	.then(console.log("nothing"))
	.then((arg) => console.log(arg)); // иначе тут бы не вывело "resolved"

Ну да, все верно. В первый then аргументом попал undefined, этот then проигнорировался и дальше все пошло, как нужно.

Да, undefined попал аргументов в then, но это же не значит, что then его передает дальше.

Так я об этом и говорю, .then(logger(2)) в примере не вернет undefined и не передаст его в следующий .then, а просто отработает. Хотя формулировка немножко скользкая.

Ну да, по поводу формулировки согласен.

Все верно, вызов logger(2) вернет undefined, но полностью .then(logger(2)) не сделает ничего.

Да, наверное возникло небольшое недопонимание. Я это и имел в виду — именно .then (а не колбек в .then) принимает undefined, соответственно игнорируется полностью.

Да, наверное возникло небольшое недопонимание. Я это и имел в виду — именно .then (а не колбек в .then)
Да, тут немного конфузит слово «передастся» (т.к. игнорируется аргумент undefined, а не .then). Не говорю, что вы что-то неправильно написали.

ps: я сам чуть выше написал так как вы) и понял, что этот момент можно неправильно понять и надо прояснить

Не совсем понимаю вашу логику и пожалуй не соглашусь. Моя же логика подсказывает мне следующее:

console.log("nothing")

все таки вернет undefined, который передастся аргументом в then(). Соответственно then() получает первым параметром не функцию а undefined и этот шаг должен полностью игнорироваться.

Еще раз посмотрим что пишет по этому поводу стандарт (пункты 2.2.7.3 и 2.2.7.4):

promise2 = promise1.then(onFulfilled, onRejected);

If onFulfilled is not a function and promise1 is fulfilled, promise2 must be fulfilled with the same value as promise1.
If onRejected is not a function and promise1 is rejected, promise2 must be rejected with the same reason as promise1.

То есть, в вашем примере колбек в последнем then() получит результат из

(new Promise(resolve => resolve("resolved")))

Хотелось бы подробней, если не сложно.

Опять очередной топик (и не только на ДОУ) на тему «Промисы работают не так, как хотелось бы»...

Ну, мне промисы нравятся гораздо больше, чем не промисы, но, как по мне, то вышеописанное — это провтык... Или в таком поведении есть смысл, который я не вижу?
PS об этом поведении я раньше статей не видел.

Это просто логичное поведение. Да, может стоит сунуть warning связанный с этим, а может и нет.
Фактически, это аналогично элементарному:

function a () {
  console.log(1);
}

function b () {
  console.log(2);
}

a(b());

Как и ожидается, будет «2 1». То же самое и с промисами. Во второй .then() в вашем примере попадет результат logger(2). И все. Как написано, так и работает.

Хотите, чтобы работало, как с «browser.wait»? Кто вам мешает — возвращайте промис в logger.

Я думаю, что, если функция ждёт конкретных аргументов и не получает их или получает что-то другое, то варнинг — это как минимум. Но топик я написал, дабы предотвратить чьи-то ошибки и показать как это работает + убедиться, что я сам правильно понял этот момент, а не раскритиковать промисы.

Ну так ошибка не в промисе, а в непонимании, что произойдет, если вызвать функцию. Или, как всегда, желание чтобы работало так, как хочется, а не как написано. Без обид.

В чём непонимание? Я что-то неправильно написал? А желание, да, есть ) Я вот пока не много работал с этим счастьем и не встречал и не могу придумать случая, когда мне в зенчейне нужно было бы вызвать функцию, а не передать колбек. Следовательно, отсюда и хотелка варнинга или иксепшина. Если случаи, когда так нужно сделать есть, то, может, оно тогда и лишнее. Не подскажите?

Ну, такой пример вы привели сами:

.then(browser.wait(time))

Правильно, я встретил это в коде и написал, чтоб так не делали. В чём неправота?

Ну люди читают книги СО копипейст, а не основы JS

Підписатись на коментарі