Комментарии 119
Судя по ридми на github в библиотеке отсутствует обработка ошибок
Following operators are not yet supported:
- const
- let
- for… of
- try… catch
а значит вся её ценность равна нулю.
Тот же костыльPromise отлично с этим справляется.
Кстати, так вами любимый async/await тоже использует Promise
When async function is called, it returns a promise. When the async function returns a value, the promise will be resolved with the returned value. When the async function throws an exception or some value, the promise will be rejected with the thrown value.
Async function can contain await expression, that pauses the execution of the async function and waits for the passed promise's resolution, and resumes the async function's execution and returns the resolved value.
подробнее можно например тут посмотреть https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
Кстати, так вами любимый async/await тоже использует Promiseда, но async/await стал возможен не благодаря Promises, а благодаря генераторам, которые нативно позволяют останавливать/возобновлять выполнение контекста в движке. Чтобы получить Promises достаточно подключить небольшой полифил, а вот чтобы получить остановку/возобновление контекста исполнения в ES2015 в Babel фактически пришлось создать State machine, парсить и эмулировать исполнение операторов функции примерно так же, как это делает SynJS.
На ноде уже есть async/await.
плюс похоже будет проблема с замкнутыми переменными вне SynJS.run
var i=123;
function myTestFunction1() {
console.log(i); <--- i будет undefined
}
SynJS.run(myTestFunction1,obj, function () {
console.log('done all');
});
К ней надо относится так, как если бы она была определена где-то в другом модуле, и передавать необходимые переменные через параметры, obj (this внутри функции), или global.
SynJS.run(myFetches,null,modules,urls,function () {
console.log('done');
});
Если после завершения нужно выполнить еще что-то с полученным результатом, то это будет выглядеть так?
SynJS.run(myFetches,null,modules,urls,function () {
SynJS.run(myAfterFetches,null,modules,??result?? /*где бы его получить*/,function () {
console.log('done');
});
});
или есть техника как избежать SynjsHell, простите за каламбур
function myFetches(modules, urls) {
...
return 123;
}
SynJS.run(myFetches,null,modules,urls,function (res) {
console.log(res); <-- напечатает 123
});
Можно вызывать вложенные SynJS.run, в этой части все как в обычном JavaScript. Ограничения касаются, в основном, функции, которая исполняется через SynJS.run,
В 3-м примере показано как SynJS.run вызывается рекурсивно чтобы обойти дерево.
В 3-м примере показано как SynJS.run вызывается рекурсивно чтобы обойти дерево.
я имею ввиду после того как мы получили дерево и хотим в с ним что-то сделать. Например отфильтровать узлы.
Можно вызывать вложенные SynJS.run, в этой части все как в обычном JavaScript
ну то есть от callback hell мы никуда не ушли?
SynJS.run(myFetches,null,modules,urls,function (res) {
// обработка ошибки 1?
SynJS.run(filterTree, null,modules,res,function (res) {
// обработка ошибки 2?
SynJS.run(doSomethingWithFilteredTree, null,modules,res,function (res) {
// обработка ошибки 3?
console.log(res);
});
});
});
@amaksr так?
ну то есть от callback hell мы никуда не ушли?
Мы ушли от callback-hell только внутри функции, вызываемой через SynJS.run. Все остальные функции подчиняются тем же законам JavaScript, что и раньше. Точно так же в случае async/await мы должны объявить функцию через async, если мы собираемся в ней ждать коллбеки (ну и плюс еще сделать оболочки с Promises для функций с колбеками, которые мы собираемся вызывать).
Вообще этот момент мне более всего непонятен: почему нельзя было ввести в JavaScript оператор, который бы приостанавливал исполнение контекста без блокировки других контекстов, лет так 10 назад? Тогда никто и не знал бы сейчас про callback hell. Почему только недавно такая возможность появилась, но и то в виде генераторов? Выглядит так, что кто-то сильно ошибся с дизайном когда-то давно, поэтому мы сейчас и имеем все эти костыли.
В NodeJS эта возможность появилась 6 лет назад.. А не стандартизировали это потому, что дизайном языка занимаются не грамотные архитекторы, а толпа леммингов.
Не хотите ли добавить SynJS в эту коллекцию асинхронных паттернов? https://github.com/nin-jin/async-js
Что предложите добавить, чтобы это обрело смысл?
Но ваша задача натолкнула меня на мысль, что в хорошо бы добавить возможность приостанавливать не только операторы в некоторой функции, но и вычисление выражений. Тогда код, который сейчас в SynJS выглядит так:
var res1 = query("select 1");
SynJS.wait();
var res2 = query("select 2");
SynJS.wait();
var res = res1 + res2;
можно было бы сократить до
var a = query("select 1") + query("select 2");
Наверное попробую это реализовать…
Не пойму, почему немного не потерпеть пока async/await пойдет в масссы а пока пересидеть на babel?
Все бизнесы, знаковые мне изнутри, поддердживают 2+ старых версий IE, для которых писать быстрый JS очень не просто.
Про async/await чистая ложь
Во-первых, transform-async-to-generator просто заменяет все await на yield и оборачивает функцию в вызов функции co (github.com/tj/co, можно свою реализацию подставить). Также есть asynctogen, с которым нет смысла тащить babel, если других фич не используете
Во-вторых, у вас включен regenerator, и поэтому код страшный
regenerator преобразует генераторы в стейт-машину которая будет работать и на старых версиях V8, которые не поддерживают их.
А async/await может спокойно без него работать, если упомянутые выше генераторы поддерживаются движком (благо они уже более распространены)
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
Если хром будет не 55, а 54 версии, он, судя по всему, не будет поддерживать async. Как поддержка генераторов ему поможет?
Еще про генераторы забыли. Они поддерживаться уже давно.
global.SynJS = require("synjs");
function publishLevel(modules) {
var levels=[];
var start = new Date().getTime();
for(var i=0; i<100000; i++) {
var user = modules.getUser(i);
var can_create = modules.canCreate(user);
if(!can_create)
var level = modules.saveLevel(user, null);
levels.push(level);
}
return new Date().getTime()-start;
}
function getUser(user_id) {
return {
id: user_id,
nickname: 'tlhunter'
};
}
function canCreate(user) {
return user.id === 12;
}
function saveLevel(user, data) {
return {
id: 100,
owner: user.nickname,
data: data
};
}
var modules = {
getUser: getUser,
canCreate: canCreate,
saveLevel: saveLevel
};
SynJS.run(publishLevel,null,modules,function(ret){
console.log('ret=',ret);
})
В SynJS она завершилась за 87мс. Эквивалентный код с одним async, 3-мя await-ами, промисами (но без setTimeout-ов и других длительных функций) после Babel-я работал около 3 с.
Нативные генераторы не проверял.
Ко всему прочему хотел бы докинуть что у вас же вышли старые добрые fiber'ы и они уже давно есть в NodeJS как одно из решений но от них отказываются. Как-то генераторы надежнее выглядят и сводятся к тому же.
оно парсит код. От этого мало того, что отваливается вся оптимизация, которые делает браузер, но и уж точно вы не учли все возможные виды написания кода, который уж точно не уместится в 34kb.
Просто используйте Promise, и не нужно мучить ни себя не других. Если кто то внезапно столкнется с проблемами, которые вызывает ваш велосипед — цена переписывания кода с синхронного с костылями на асинхронный будет слишком высока, проще сразу писать нормально.
Да и полифил promise не минифицированный занимает всего 8kb, а уменьшенный 3kb.
Promise не поможет мне написать понятный код, в котором перемешаны функции с колбеками, циклы, условия и рекурсия. Написать то конечно можно, но достаточно взглянуть на StackOverflow — там каждый день идут десятки вопросов как реализовать тот или иной алгоритм с Promises, и предлагаемые решения не выглядят интуитивно понятно.
Велосипед не засчитан, так как не работает в ряде случаев. Вариант с парсингом затирает контекст исполнения, т.о. я не могу быть уверен в том, что функция полученная откуда-то будет работать предсказуемым образом.
Почему они с тем же успехом не могли распарсивать функцию и впиливать поддержку await/async? Если это так хочется делать в рантайме, а не транспилировать.
Парсер классный, аж волосы зашевелились )
Простой? Посмотрите как выглядит нормально и понятно написанный парсер рекурсивного спуска: https://github.com/angrycoding/javascript-parser/blob/master/src/parser/Parser.js
Кстати, если хотите реально сделать что то полезное (в первую очередь для себя), то вместо костылей напишите ограниченный сабсет джаваскрипта, с поддержкой всего того что Вам нужно и компилятором в обычный JS.
А так да, велосипед тот ещё, во — первых рантайм, а во — вторых потеря контекста.
Какое уродливое не-решение никаких проблем. Не знаю зачем вы его придумали, и для чего это может пригодиться. Возьмите уж тогда хотя бы promise-hell, всяко менее вырвиглазнее, да и без runtime парсинга js-а.
Но вот одну интересную мысль из топика я таки выцепил. Вы упомянули о том, что babel-polyfil весит под 100 KiB. Я с возмущением и словами "да не может быть, какая ерунда" полез смотреть и опешил. 97 KiB. Гхм. Грусть-печаль меня охватила.
Не очень верится, что вы не используете ни одной сторонней библиотеки.
Что же мешает его добавить?
Это скорее computed паттерн. Есть он много где, но зачастую реализация не эффективна. Оцените масштаб трагедии.
Про атомы. Тут про них подробнее. Впрочем, CanJS тоже поддерживает "computed".
Да, ()=>
— это та же лямбда. То же что и function(){ ... }
, только сохраняет this.
Нужен любой современный браузер, нода или какой-нибудь транспилятор. Лично я пользуюсь тайпскриптом.
Я же написал или.
TortoiseGit, например.
Для вопросов есть специальный ресурс: https://toster.ru
А так, в Chrome Dev Tools можно смотреть обработчики повешенные на выделенный элемент и всех его предков.
Бонусом к этому способу идет соблюдение SRP, нормальное юнит-тестирование и все такое.
На данный момент я использую SynJS для написания браузерных тестов
Т.е. там, где даже мегабайты «лишнего» кода некритичны, да ещё и не продакшен это.
Ну и даже не заглядывая в исходники ставлю на полную неработоспособность при использовании любого обфускатора.
Будущее, судя по всему, за async/await, но пока это будущее не наступило, и многие движки эту возможность не поддерживают.
Кроме того, Babel тянет за собой около 100 кб минифицированного кода «babel-polyfill», а сконвертированный код работает медленно
Посмотрев на все это, я решил написать свой велосипед — SynJS свою урезанную версию реализации async/await
на голой Эсприме...
Насчет обфускатора вы скорее всего правы, но иной раз код с Promises, логикой с рекурсией, циклами и колбеками выглядит так, что никакого обфускатора и не надо.
Ой, ну не надо, продуманная декомпозиция решат всё.
Забыл добавить: Но, обычно, причина callback/promise hell, банальная лень.
Блин, хабр, что ты сделал.
Насчет обфускатора вы скорее всего правы, но иной раз код с Promises, логикой с рекурсией, циклами и колбеками выглядит так, что никакого обфускатора и не надо.
Ой, ну не надо, продуманная декомпозиция решат всё.
Вот вы пишите про какие-то там задачи, в которых Callback/Promise страшны, но в статье их не приводите, а показываете какие-то элементарные вещи.
Потом идет пример на async/await, дальше пугаем babel-polyfill
(который содержит поддержку генераторов, итераторов и много чего), говорим что спасение есть, это SynJS
и… не приводим примера той же задачи на неё, а показываем какую-то муть myTestFunction1
и c setTimeout
.
P.S. Обфускатор (он же и минификатор), сейчас используют все по умолчанию.
Серьезно? Вот это лучше промисов?
Буду этим джунов пугать!
fetch(“list_of_urls”, function(array_of_urls){ for(var i=0; array_of_urls.length; i++) { fetch(array_of_urls[i], function(profile){ fetch(profile.imageUrl, function(image){ ... }); }); } });
Почему в качестве решения люди предлагают использовать сторонние библиотеки, и даже хотят вводить async/await, которые по факту не решают проблему, а лишь маскируют ее?
Разве не легче просто писать хороший код, который будет сам за себя говорить, что он делает?
fetch(“list_of_urls”, _loadAllFetchedUrls);
function _loadAllFetchedUrls(array_of_urls) {
for(var i=0; array_of_urls.length; i++) {
fetch(array_of_urls[i], _loadProfileImage);
}
}
function _loadProfileImage(profile){
fetch(profile.imageUrl, _showProfileImage);
}
В данном случае все функции будут поддаваться оптимизации со стороны js движка, не будет генерироваться лишний мусор, сам код становится читабельным — имя функции сразу говорит, что будет сделано после загрузки.
Есть недостаток в том, что всю цепочку не видно, но это еще ни разу не было критичной проблемой, при этом любая более-менее хорошая IDE покажет всю цепочку вызовов до конкретного callback'а.
По-моему такой подход решает проблему «callback hell» без каких-либо сторонних библиотек и без нововведений на уровне языка (async/await).
Я, конечно, могу ошибаться и если это так, буду рад услышать в чем именно я ошибся.
По-моему такой подход решает проблему «callback hell» без каких-либо сторонних библиотек и без нововведений на уровне языка (async/await).
Вы превращаете вложенную-лапшу в вертикальную-лапшу. Никаких проблем вы этим НЕ решаете. Любой нетривиальный случай вырождается при таком подходе в груду методов с дурацкими названиями, с кучей аргументов и многочисленной копипастой.
async openChild(id)
{
try
{
const cached = id in this.state.map;
let widget = cached && this.state.map[id];
if(!cached)
{
this.updateHabData(this.activeId, { habLoading: true });
widget = await this.load(id);
this.updateHabData(this.activeId, { habLoading: false });
}
await this.updateHabData(id,
{
id,
title: widget.title,
widget,
loading: false,
habLoading: false,
failed: false
});
this.setActiveId(widget.id);
this.navigator.pushPage(this.createRoute(widget));
if(cached)
{
const widget = await this.load(id); // обновляем
this.updateHabData(id, { widget });
}
}
catch(err)
{
this.updateHabData(this.activeId, { habLoading: false });
this.notifyError(err, this.l('failLoad'));
}
}
Его можно переписать в promise-стиле. Хотя он при этом значительно потеряет в наглядности. А если переписать его на множество разрозненных методов с кучей callback-ов без замыканий, то потребуется куча документации, длинные идиотские названия методов и пр., чтобы в этом вообще не сломать ногу.
А если этот openChild сам по себе является частью нетривиальной цепочки? Вам придётся плодить много-много сущностей на любой чих, а чтобы многочисленные их методы не путались с друг другом, придётся их ещё изолировать в разные объекты/классы. И так как без контекста такой код будет предельно неочевидным, придётся строчить много-много комментариев, примеров и прочего. А когда выяснится, что половину нужно переделать, т.к. условия изменились… упс. Трагедия.
Обычно приводят тривиальные и глупые примеры, которые даже под разряд callback hell не подходят.
Это какой-то, кхм, «странный» метод, где-то просто this.updateHabData
, где-то await this.updateHabData
, wtf?
this.updateHabData(this.activeId, { habLoading: true });
widget = await this.load(id);
— порядок выполнения случайный.
В конкретно этом коде проблем быть не должно, но в целом, это может породить гонку выполнения! (особенно на запросах к серверу) и соответственно вылиться в эпический факап. Но кого это волнует?!
Код с душком, будем честными (проверка на cached
вызывает недоумение, как и в целом работа с updateHabData
?!).
А в чём собственно проблема? :) Расстановка await перед всеми вызовами updateHabData по сути ничего не поменяет. Это так, экономия на спичках (и муках отладки регенератора в случае чего), наверное избыточная, но никакой трагедии уж точно. Не думал что она кого-то смутит.
А что не так с проверкой на cached? Она тут нужна ввиду того, что возможны два сценария:
- Данные берутся из кеша и отображаются моментально. В этом случае показываем их сразу, но втихую догружаем обновление
- Данных в кеше нет, и мы вынуждены показывать спиннер, дожидаясь загрузки.
Отдельного упоминания заслуживает onsen-ий navigator.pushPage, которые занимается разного рода анимациями перелистывания экранов (ради чего и приходится городить столько кода).
Расстановка await
в произвольно порядке говорит только о том, что над кодом особо не думали и так сойдёт.
С cached
тоже самое, метод openChild
умеет работать с кешем, загружать данные, показывать спиннеры, что-то обновлять (но при этом сам кеш не кладёт), наверно и кофё варить ;] Всё перемешалось в этом методе.
Повторяю, await расставлены не в произвольном порядке, а в таком, что await-ятся только те операции, дождаться окончания которых критично (тут нет никаких race conditions). Повторюсь, внутри .setState от react-компоненты. Часто вы в своём коде передаёте туда callback? Думаю крайне редко, однако такие ситуации могут быть полезными. В данном случае одна такая в коде показана, т.к. state обязательно должен обновиться до того, как будет вызван onsen.navigator.pushState (иначе он упадёт не найдя нужных для render-а данных).
Всё перемешалось в этом методе.
Соглашусь только с этим. Но, увы, некоторые UI задачи требуют заморочек. А cache обновляется внутри load-а.
await выполняет не только функцию "приостановить в ожидании успешного выполнения асинхронной функции", но и "кинуть исключение в случае безуспешного выполнения асинхронной функции". В случае ошибки в updateHabData вы не сможете её никак обработать. Собственно это — основная проблема асинхронного кода — нужно очень внимательно следить за обработкой ошибок при каждом асинхронном вызове и очень легко ошибку проигнорировать, оставив приложение в поломанном состоянии.
Я просто расставил await только в тех местах, где критично дождаться окончания его выполнения (там внутри react-ий setState, который может быть обработан асинхронно). Где не критично пустил его на самотёк.
deleted, не туда :(
Еще один велосипед для борьбы с callback hell в JavaScript