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 с.
SynJS лучше справляетс с задачами, где перемешаны колбеки, цикли, условия и рекурсии.
Но ваша задача натолкнула меня на мысль, что в хорошо бы добавить возможность приостанавливать не только операторы в некоторой функции, но и вычисление выражений. Тогда код, который сейчас в SynJS выглядит так:
var res1 = query("select 1");
SynJS.wait();
var res2 = query("select 2");
SynJS.wait();
var res = res1 + res2;
Добавить можно, но так как в предложенном тесте всего лишь одна асинхронная операция, а в функциях практически нет логики, то смысла это особого не имеет, и код только раздуется.
Да, банальная лень, только эта лень разработчиков самого языка JavaScript, которые в течение долгих лет не давали возможность приостанавливать функции нативно и без блокировки, что собственно и породило саму проблему callback hell.
Интересно, как любое упоминание IE моментально набирает минусы. Но вот я сейчас работаю с 2-мя западными компаниями, которые сидят на Windows 7 и IE 9, и обновлений на Edge даже и не планируют пока. Они совместимость со своим старьем всегда ставят в условия ТЗ. Не отказывать же им…
Инструмент новый, пока обкатывается в тестах, вроде показывает себя хорошо, а значит будет и в продакшене.
Насчет обфускатора вы скорее всего правы, но иной раз код с Promises, логикой с рекурсией, циклами и колбеками выглядит так, что никакого обфускатора и не надо.
Парсер пролучился довольно простой так как он парсит функцию, которая уже откомпилирована движком, а значит имеет корректный синтсксис. Если бы нужно было парсить произвольный текст, то парсер бы раздулся во много раз, и в 35кб он бы не уложился.
Это решение для очень простой проблемы, так как все итерации однотипные, и не связаны никакой логикой. Такую проблему можно решать легко и через рекурсию, и через Promises, и через async.js. Все становится гораздо сложнее если вам надо делать циклы или условия в каждой итерации, и по их результатам вызывать другие асинхронные функции и ждать колбеков от них
Генераторы поддерживаются не везде. Они необходимы чтобы нативно приостанавливать выполнение функции и реализовать ожидание чего-то (возможность приостанавливаться и ждать как раз и позволиляет избавится от колбеков), но в IE например их нет. Поэтому Babel фактически создает свою State machine, парсит код функции и исполняет ее операторы сам, без вызова этой функции напрямую.
Распарсенные операторы в SynJS компилируются в функции через eval (насколько я знаю Node тоже загружает файлы через eval). Поэтому все оптимизации, происходящие в eval никуда не отвалятся. К тому же делается это только 1 раз при первом вызове функции.
Promise не поможет мне написать понятный код, в котором перемешаны функции с колбеками, циклы, условия и рекурсия. Написать то конечно можно, но достаточно взглянуть на StackOverflow — там каждый день идут десятки вопросов как реализовать тот или иной алгоритм с Promises, и предлагаемые решения не выглядят интуитивно понятно.
Мы ушли от callback-hell только внутри функции, вызываемой через SynJS.run. Все остальные функции подчиняются тем же законам JavaScript, что и раньше. Точно так же в случае async/await мы должны объявить функцию через async, если мы собираемся в ней ждать коллбеки (ну и плюс еще сделать оболочки с Promises для функций с колбеками, которые мы собираемся вызывать).
Вообще этот момент мне более всего непонятен: почему нельзя было ввести в JavaScript оператор, который бы приостанавливал исполнение контекста без блокировки других контекстов, лет так 10 назад? Тогда никто и не знал бы сейчас про callback hell. Почему только недавно такая возможность появилась, но и то в виде генераторов? Выглядит так, что кто-то сильно ошибся с дизайном когда-то давно, поэтому мы сейчас и имеем все эти костыли.
Можно вызывать вложенные SynJS.run, в этой части все как в обычном JavaScript. Ограничения касаются, в основном, функции, которая исполняется через SynJS.run,
В 3-м примере показано как SynJS.run вызывается рекурсивно чтобы обойти дерево.
Функция, вызываемая через SynJS.run, ничего не будет знать о своем окружении, так как она не вызывается JS-движком напрямую:
var i=123;
function myTestFunction1() {
console.log(i); <--- i будет undefined
}
SynJS.run(myTestFunction1,obj, function () {
console.log('done all');
});
К ней надо относится так, как если бы она была определена где-то в другом модуле, и передавать необходимые переменные через параметры, obj (this внутри функции), или global.
try...catch недоступно только в теле самой вызываемой функции, которая запускается через SynJS.run (myTestFunction1 в статье). В любых других функциях, в том числе вызываемых из myTestFunction1 try...catch доступен.
Кстати, так вами любимый async/await тоже использует Promise
да, но async/await стал возможен не благодаря Promises, а благодаря генераторам, которые нативно позволяют останавливать/возобновлять выполнение контекста в движке. Чтобы получить Promises достаточно подключить небольшой полифил, а вот чтобы получить остановку/возобновление контекста исполнения в ES2015 в Babel фактически пришлось создать State machine, парсить и эмулировать исполнение операторов функции примерно так же, как это делает SynJS.
В SynJS она завершилась за 87мс. Эквивалентный код с одним async, 3-мя await-ами, промисами (но без setTimeout-ов и других длительных функций) после Babel-я работал около 3 с.
Нативные генераторы не проверял.
Но ваша задача натолкнула меня на мысль, что в хорошо бы добавить возможность приостанавливать не только операторы в некоторой функции, но и вычисление выражений. Тогда код, который сейчас в 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");
Наверное попробую это реализовать…
Насчет обфускатора вы скорее всего правы, но иной раз код с Promises, логикой с рекурсией, циклами и колбеками выглядит так, что никакого обфускатора и не надо.
Promise не поможет мне написать понятный код, в котором перемешаны функции с колбеками, циклы, условия и рекурсия. Написать то конечно можно, но достаточно взглянуть на StackOverflow — там каждый день идут десятки вопросов как реализовать тот или иной алгоритм с Promises, и предлагаемые решения не выглядят интуитивно понятно.
Мы ушли от callback-hell только внутри функции, вызываемой через SynJS.run. Все остальные функции подчиняются тем же законам JavaScript, что и раньше. Точно так же в случае async/await мы должны объявить функцию через async, если мы собираемся в ней ждать коллбеки (ну и плюс еще сделать оболочки с Promises для функций с колбеками, которые мы собираемся вызывать).
Вообще этот момент мне более всего непонятен: почему нельзя было ввести в JavaScript оператор, который бы приостанавливал исполнение контекста без блокировки других контекстов, лет так 10 назад? Тогда никто и не знал бы сейчас про callback hell. Почему только недавно такая возможность появилась, но и то в виде генераторов? Выглядит так, что кто-то сильно ошибся с дизайном когда-то давно, поэтому мы сейчас и имеем все эти костыли.
Можно вызывать вложенные SynJS.run, в этой части все как в обычном JavaScript. Ограничения касаются, в основном, функции, которая исполняется через SynJS.run,
В 3-м примере показано как SynJS.run вызывается рекурсивно чтобы обойти дерево.
К ней надо относится так, как если бы она была определена где-то в другом модуле, и передавать необходимые переменные через параметры, obj (this внутри функции), или global.
да, но async/await стал возможен не благодаря Promises, а благодаря генераторам, которые нативно позволяют останавливать/возобновлять выполнение контекста в движке. Чтобы получить Promises достаточно подключить небольшой полифил, а вот чтобы получить остановку/возобновление контекста исполнения в ES2015 в Babel фактически пришлось создать State machine, парсить и эмулировать исполнение операторов функции примерно так же, как это делает SynJS.