Comments 119
Стоило ли делать все это ради неявного async/await с переусложненным интерфейсом? В современном javascript не используются коллбэки, насколько мне известно — их место заняли Promise, которые, в свою очередь, пишутся в псевдосинхронном стиле через await.
А async/await все еще не доступен на многих браузерах, особенно в крупных компаниях. Ну не отказывать же им, если они требуют совместимости с IE…
все еще не доступен на многих браузерах, особенно в крупных компаниях.
Казалось бы, именно ради этого и был написан babel...
А этот движок прям сразу в крупных компаниях возьмут на вооружение...
позволяет в том числе подготовить код к переходу на async await. кроме того поддерживает thunk, т.е. можно работать со старым кодом на колбэках как если бы это были промисы
— в nsynjs нет надобности использовать ключевые слова async/await в коде, так как тип исполняемой функции проверяется в рантайме
— в nsynjs отпадает надобность в промисах в принципе (хотя для поклонников можно добавить несколько строчек в код nsynjs чтобы проверять возвращаемый функцией результат в рантайме на предмет не промис ли он, и не надо ли подождать).
— в nsynjs для исключительных ситуаций достаточно механизма try/catch/throw. Механизм Promise/then/catch/reject не нужен.
Преимущества по-сравнению с async/await:
— возможность запускать псевдо-потоки,
— возможнось останавливать псевдо-потоки как изнутри, так и извне,
— возможнось подчистить активные функции с колбеками при остановке потока извне (например на активный setTimeout автоматически вызвать clearTimeout),
— возможность создавать конструкторы с асинхронными функциями внутри.
Что такое "возможность запускать псевдо-потоки" и почему это преимущество перед async/await?
— в nsynjs нет надобности использовать ключевые слова async/await в коде, так как тип исполняемой функции проверяется в рантайме
и это плохо. Javascript неспроста движется в сторону проверки всего и вся при компиляции, включая статическую типизацию.
— в nsynjs отпадает надобность в промисах в принципе (хотя для поклонников можно добавить несколько строчек в код nsynjs чтобы проверять возвращаемый функцией результат в рантайме на предмет не промис ли он, и не надо ли подождать).
прелесть промисов в явном, детерминированном управлении event loop — см. концепции greenlets, fibers и т.д. В nsynjs этого нет, и по дизайну невозможно.
— в nsynjs для исключительных ситуаций достаточно механизма try/catch/throw. Механизм Promise/then/catch/reject не нужен.
Наверное, я неправильно использовал промисы все это время?
async function foo() {
try {
await bar();
} catch(e) {
// ...
} finally {
// ...
}
}
— возможность запускать псевдо-потоки,
В чем отличие от запущенной async-функции?
— возможнось останавливать псевдо-потоки как изнутри, так и извне,
В чем отличие от bluebird, который умеет отменять исполняющиеся промисы?
— возможнось подчистить активные функции с колбеками при остановке потока извне (например на активный setTimeout автоматически вызвать clearTimeout),
В чем отличие от обертки try/finally внутри тела async-функции, с clearTimeout внутри finally?
— возможность создавать конструкторы с асинхронными функциями внутри.
А за асинхронные конструкторы, как по уму, надо бы отрывать руки по самые ягодицы. Как, например, за асинхронные геттеры, асинхронные сеттеры, асинхронные присваивания и т.д. Если нужно сконструировать объект асинхронно — сделай статический метод-фабрику, который соберет всю нужную информацию и передаст ее в синхронный конструктор, так как объекты должны быть копируемыми. Конструктор с сайд-эффектами — это в принципе признак омерзительного дизайна.
Javascript неспроста движется в сторону проверки всего и вся при компиляции
В JS изначально даже var не было, да и сейчас практически все резольвятся в рантайме, поэтому я бы не стал брать JS в качестве примера как надо делать типизацию.
прелесть промисов в явном, детерминированном управлении event loop — см. концепции greenlets, fibers и т.д. В nsynjs этого нет, и по дизайну невозможно.в nsynjs свой event loop, а также свои структуры с программными счетчиками, стеками, локальными переменными, closures и т.п.
Наверное, я неправильно использовал промисы все это время?
Имеется ввтиду внутри промисифицированных функций, или если промис вернули куда-то в не async-функцию
В чем отличие от запущенной async-функции?в том что есть указатель на ее состояние, с её собственным event-loop-ом, по которому над ней можно иметь полный контроль.
В чем отличие от bluebird, который умеет отменять исполняющиеся промисы?
В блюберд придется писать код чтобы отслеживать активные промисы. Хотя это и не сложно. В нативных промисах этого нет, и, говорят, не будет.
В чем отличие от обертки try/finally внутри тела async-функции, с clearTimeout внутри finally?Это придется делать везде, где вызывается промис с setTimeout? Либо делать async-обертку к промису, ну и отслеживать активные обертки. В nsynjs это делается автоматически, т.к. есть свой стек, по которому можно всегда узнать что сейчас активно.
А за асинхронные конструкторы, как по уму, надо бы отрывать руки по самые ягодицы.Не буду холиварить. Видел их много раз, и не сказал бы, что изза них какие-то существенные проблемы. Это как goto, кому-то нравится, кому-то нет…
В JS изначально даже var не было, да и сейчас практически все резольвятся в рантайме, поэтому я бы не стал брать JS в качестве примера как надо делать типизацию.
В JS изначально много чего не было. Тот JS, который имеем сейчас, и тот, с которого начиналось, это два разных языка, с разными ценностями и идиомами. И современный JS семимильными шагами идет к сильной типизации с выведением типов.
в nsynjs свой event loop, а также свои структуры с программными счетчиками, стеками, локальными переменными, closures и т.п.
И все это, конечно же, неявное. И, кстати, зачем это все нужно, ведь рантайм уже все предоставляет?
Имеется ввтиду внутри промисифицированных функций, или если промис вернули куда-то в не async-функцию
Если вернули не в async-функцию, она с ним работать все равно не сможет, поэтому и исключение ловить не надо, т.к. исключение все равно не будет выброшено в контексте этой функции.
в том что есть указатель на ее состояние, с её собственным event-loop-ом, по которому над ней можно иметь полный контроль.
Насколько мне известно, запустить несколько event loop в одном потоке нельзя по определению этого самого event loop, т.к. каждый event loop должен выполнять ожидание событий на своем списке дескрипторов средствами операционной системы. Значит, каждая функция работает в отдельном потоке? Великолепно! И в таком случае, в чем отличие от WebWorkers? И как у вас решился вопрос отсутствия в JS любых механизмов многопоточной синхронизации?
В блюберд придется писать код чтобы отслеживать активные промисы. Хотя это и не сложно. В нативных промисах этого нет, и, говорят, не будет.
Имеющийся proposal был отозван из-за излишней сложности реализации в v8, насколько мне известно. придумают способ проще — будет.
Это придется делать везде, где вызывается промис с setTimeout? Либо делать async-обертку к промису, ну и отслеживать активные обертки. В nsynjs это делается автоматически, т.к. есть свой стек, по которому можно всегда узнать что сейчас активно.
"Explicit is better than implicit. Simple is better than complex." Я не понимаю, в чем проблема явно освободить занятый ресурс (да, таймер это ресурс), как не вижу проблемы в том, чтобы закрыть за собой сокет или файл. Что делать, если я хочу из nsynjs передать объект таймера за пределы вашей RAII-процедуры? Мне его убьет при выходе из процедуры, несмотря на то, что референс утек? Если не убьет, тогда в чем смысл? Если убьет, то как этим пользоваться?
Ну и я не понимаю, зачем в принципе нужно "отслеживать активные обертки". Асинхронные процедуры на то и асинхронные, что это не потоки, и не должны ими быть — асинхронные процедуры по определению исполняются в одном потоке посредством кооперативной многозадачности. В отслеживании и убийстве асинхронных процедур я вижу непонимание этого факта и попытку сделать менеджер процессов для асинхронных процедур. Но у рантайма нет задачи управлять "процессами", более того — в ни одном языке программирования невозможно чисто завершить тред, так как убив тред, программа автоматически переходит в неопределенное состояние (кроме случаев, когда треды не имели никаких общих данных, никаких глобальных ресуров и т.д.).
Мне бы хотелось больше узнать о мотивации, зачем это было сделано, и какие именно задачи это призвано решать, потому что, как мне кажется, очевидно, что с async/await это решение не конкурент.
Насколько мне известно, запустить несколько event loop в одном потоке нельзя по определению этого самого event loop, т.к. каждый event loop должен выполнять ожидание событий на своем списке дескрипторов средствами операционной системы.
Они могут вкладываться друг в друга. Один event-loop может быть задачей в рамках другого event-loop. Очевидно, тут именно такой случай.
в ни одном языке программирования невозможно чисто завершить тред, так как убив тред, программа автоматически переходит в неопределенное состояние (кроме случаев, когда треды не имели никаких общих данных, никаких глобальных ресуров и т.д.).
Erlang, D
зачем в принципе нужно «отслеживать активные обертки»в случае, если псевдопоток nsynjs завершается извне (типа как по SIGHUP), и надо освободить ресурсы, инача они вызовут колбеки. Стандартный JS такие возможности не предоставляет, ну тоесть надо писать учет активных функций самому. Но раз уж у нас есть свой event-loop и свои стеки, то почему бы не реализовать чтобы это делалось автоматически?
прелесть промисов в явном, детерминированном управлении event loop — см. концепции greenlets, fibers и т.д.
Нет, эти концепции не требуют никакой "явности". Вообще, странно слышать термин "явность" от человека, использующего async, который неявно превращает функцию в конечный автомат.
Какая разница, если в конечном итоге все языки это дело превращают в конечный автомат? Просто не понимаю, не ужели в c++, при компиляции в asm не получается примерно такой же конструкции при вызове системных асинхронных функций?
Не превращаются. Не получается. Почти во всех языках (кроме c# и теперь уже JS) волокна реализуются через переключение стеков, что является весьма дешёвой операцией по сравнению с кучей конечных автоматов.
И с каких пор дополнительный стек является более дешевым чем конечный автомат?
Переключение стека производится только когда в этом есть необходимость (а она возникает сравнительно редко) и заключается в простом изменении указателя на стек.
Конечные автоматы же дают пенальти на каждый вызов каждой асинхронной функции. Это помимо того, что их фиг заинлайнишь. И живут такие автоматы в куче, а не в стеке.
А теперь вспоминаем про память...
Которая используется сообща всеми функциями в стеке, а не так, что для каждой функции сборщиком мусора выделяется отдельный кусок памяти.
Осталось найти способ заранее определить сколько памяти понадобится этим самым всем функциям в стеке.
Любой массив съест у вас больше лишней памяти нежели стек.
По проекту, задумка интересна, но ИМХО, это должно решаться автоматически, т.е. компилятор видит, что функция асинхронна и всегда «подставляет» await. Если же хотим получить Promise из нее и выполнить асинхронно, пишем специальную функцию (по опыту, таких ситуаций 0.1%).
это должно решаться автоматически, т.е. компилятор видит, что функция асинхронна и всегда «подставляет» awai
Согласен, именно так и должно быть. Вместо этого разработчики языка всем парят, что генераторы/промисы/async/await это круто и теперь надо всем учится программировать по-новому: массивы перебирать рекурсией, желательно хвостовой, и вообще переходить на ФП. Приходится за них это делать то, что должно было быть сделано много лет назад.
Для языка с динамической типизацией это невозможно — любая функция может оказаться как синхронной, так и асинхронной.
Возможно. Нужно уметь замораживать и размораживать текущий стек вызовов. Делать это можно либо через node-fibers, либо через бросание исключения + реактивное программирование.
Как вы себе представляете замораживание стека вызовов компилятором?
Компилятором — никак. Это в рантайме делается.
А теперь прочитайте комментарий, на который я отвечал.
Ну я не соглашусь, менять runtime javascript выйдет дороже, поэтому реально выстрелил typescript, babel и flow, а не куча компиляторов с тяжеленным или не кроссплатформенным рантаймом.
mayorovp
Компилятор вполне способен выводить типы, определять нужные ветви кода из чужого модуля (tree shaking), ну и никто не запрещает ввести ограничения на такой язык (которые так и так будут полезны).
Другое дело, как это сделать красиво и явно для разработчиков, тут нужно менять и технологии, и культуру.
В данном случае правильнее будет написать новую асинхронную фукцию, чтобы как раз, не ломать совместимость с предыдущим кодом. Введение этого «движка» в проект только ухудшит поддержку кода, по нескольким причинам:
— Новый слой абстракции. Это всегда проблема, когда этот слой не нужен.
— Неизвестная технология. Как новичкам, так и старичкам в проекте придется разбираться в работе этого модуля, придется исследовать баги, ждать пока Вы их поправите, или же править их самому.
— Не стандарт. Этого нет в ECMAScript
Ключевой вопрос "Зачем?" — не нашел ответа.
1. Если какая-то функция внизу стека стала async-await (или yield), то все вызывающие функции, и весь граф вызовов, надо также менять на async-await. Я считаю это неправильно, когда программист должен отвлекаться на такие вещи.
2. Несовместимо с некоторыми браузерами (только через babel)
Не хотите ли добавить своё решение в коллекцию? https://github.com/nin-jin/async-js/
var nsynjs = require('../../nsynjs');
function synchronousApp(modules) {
var user = require.main.require( './user' );
var greeter = require.main.require( './greeter' );
try {
console.time('time');
greeter.say('Hello', user);
greeter.say('Bye', user);
console.timeEnd('time');
}
catch(e) {
console.log('error',e);
}
}
nsynjs.run(synchronousApp,null,function () {
console.log('done');
});
var nsynjs = require('../../nsynjs');
var synchronousCode = function (wrappers) {
var config;
var getConfig = function() {
if( !config )
config = JSON.parse(wrappers.readFile(synjsCtx, 'config.json').data);
return config;
};
return {
getName: function () {
return getConfig().name;
}
};
};
var wrappers = require('./wrappers');
nsynjs.run(synchronousCode,{},wrappers,function (m) {
module.exports = m;
});
var nsynjs = require('../../nsynjs');
var synchronousCode = function(){
return {
say: function ( greeting , user ){
console.log( greeting + ', ' + ( user.getName() ) + '!' )
}
};
};
nsynjs.run(synchronousCode,{},function (m) {
module.exports = m;
});
var fs=require('fs');
exports.readFile = function (ctx,name) {
console.log("reading config");
var res={};
fs.readFile( name, "utf8", function( error , configText ){
if( error ) res.error = error;
res.data = configText;
ctx.resume(error);
} );
return res;
};
exports.readFile.synjsHasCallback = true;
Я его добавил в примеры в последнюю версию на гитхабе и в NPM, можно запускать прямо оттуда.
Время выполнения:
на десктопе i7 4790k, node v6.9.4: 3.5ms,
на ноутбуке i7 3630qm, node v6.9.4: 6.5ms,
У вас по-ссылке какие-то ошибки вместо кода, раньше вроде так не было…
require('../../nsynjs');
Может опубликуете в NPM?
require.main.require( './user' );
А почему не просто require( './user' )
?
exports.readFile = function (ctx,name) {
Может сделать это универсальным враппером идущим с самой библиотекой?
const readFileSync = synjs.fromAsync( fs.readFile )
Может опубликуете в NPM?
В NPM он есть: npm install nsynjs, и можно require('nsynjs');
require.main.require( './user' )
require, как оказалось, работает немного не так как хотелось бы с относительными путями: путь берётся относительно файла, из которого вызвана require. В случае nsynjs это значит будет относительно местоположения nsynjs.js, Пока непридумал как это победить, но наткнулся на require.main.require, которое позволяет искать относительно от начального файла приложения. require.main.require нужно только для синхронного кода.
Может сделать это универсальным враппером идущим с самой библиотекой?Да, наверное можно врапперы нескольких самых общеупотребительных функций в нее включить
Резолвить относительно мейна — тоже не вариант. Я так понимаю вы парсите переданную функцию, транспилируете код и эвалите в контексте своего модуля? В этот момент можно подменять require
на module.require
того модуля, откуда взят код. Передать модуль можно, например, так: nsynjs.run(module,synchronousApp)
...
var wrappers = require('./wrappers');
nsynjs.run(synchronousCode,{},wrappers,function (m) {
module.exports = m;
});
Можно и так, и так, кому как больше нравится.
В нативном require в node модули можно тоже грузить несколькими способами.
По-сравнению с Babel он:
- исполняется значительно быстрее,
Тем, кто просто ищет решение, способное выполняться быстрее чем Babel, могу порекомендовать попробовать tsc (Typescript Compiler).
Ставить в достоинства маленький размер по сравнению с babel некорректно, потому что babel никто в браузер не грузит, а прогоняет код на этапе сборки.
А еще, я попробовал поставить breakpoint в как-бы-синхронной функции, а он не сработал.
Как отлаживать такой код?
А как вы планируете его делать?
По факту вы исполняете не исходную функцию, а копию ее текста.
Из-за этого не только дебаггера не получится, но еще и переменные из замыкания теряются
function withParam(param) {
return function() {
console.log(param);
}
}
nsynjs.run(withParam('test'), function (r) {
console.log('Done');
});
Получаю ReferenceError: param is not defined
function synchronousCode() {
function withParam(param) {
return function() {
console.log(param);
}
};
withParam('test')();
}
nsynjs.run(synchronousCode,{}, function (r) {
console.log('Done');
});
Это связано с тем, что функции, определенные внутри синхронного кода, не работают с нативными переменными, а вместо этого модифицируются движком и используют хэш в контексте потока. Поэтому тело такой функции можно использовать только внутри синхронного кода.
А как вы планируете его делать?
Дебаггер можно отображать на динамическом div-е. Для этого нужно для каждого элемента языка в прототипы добавить функцию, которая бы выводила в смотрибельном виде данные о текущем состоянии из контекста. Ну и кнопки пошагового исполнения добавить. В общем это тривиальная задача по отображению разнородных данных из дерева на странице.
Дебаггер можно отображать на динамическом div-е. Для этого нужно для каждого элемента языка в прототипы добавить функциюВы бы себя слышали :)
Ох… Ну а если я под Node?
Вы бы себя слышали :)
А что я такого сказал? Структура с данными есть, контроль над исполнением тоже есть, причем полный. Исполняем шаг — рендерим структуру. В чем проблема?
Ох… Ну а если я под Node?Под нодой можно поднять http-сервер в том же процессе, что и приложение, и в нём точно также рендерить все на странице, которую смотреть с браузера. Понятно, что тут надо подумать, как лучше организовать код чтобы 2 раза не писать рендеринг, но принципиальных препядствий этому я не вижу.
"асинхронный дизайн" — самая большая ошибка дизайна JS. Сейчас её пытаются худо бедно исправить через async-await.
Но почему худо бедно? Не вижу в async/await как-то очевидных проблем. Сразу скажу, я не испытываю дискомфорта, когда явно видно, асинхронная функция или нет.
Дискомфорт начинается, когда синхронную функцию, которая много где используется, нужно вдруг сделать асинхронной и для этого нужно перелопатить половину проекта, превращая половину остальных функций в асинхронные.
Нет, тут всё куда печальнее — нужно менять сигнатуры всех функций, что могут оказаться выше по стеку вызовов.
Edit: Другое дело, что компилятор (TS по крайней мере) это никак не поймает…
Так и и не хочу его никуда "поднимать". Я хочу загрузить файл синхронно, но не блокируя интерфейс.
Что вам мешает это сделать?
Ну и никто не отменял веб-воркеры для таких задач.
Кажется, vintage имел в виде нечто более простое.
Если я правильно понял, он имел в виду сделать "синхронный" код отрисовки интерфейса, при этом загрузить какие-то данные асинхронно без блокировки.
Делается элементарно, без переписывания каких-либо сигнатур выше по стеку:
async function fetch(){
let data = await new Promise((done)=>{
setTimeout(()=>done('some_async_data'), 2000);
});
console.log(`Асинхронный апдейт UI после загрузки данных: ${data}`);
}
function draw(){
console.log('Синхронная отрисовка UI, шаг 1');
// даже не нужно знать, синхронная она или нет
// никаких изменений при вызове асинхронной функции...
fetch();
console.log('Синхронная отрисовка UI без блокировки на загрузку данных');
}
// ...выше по стеку тоже без изменений
draw();
Ваш код не заработает — в этом и проблема :-)
$ node async.js
Синхронная отрисовка UI, шаг 1
Синхронная отрисовка UI без блокировки на загрузку данных
Асинхронный апдейт UI после загрузки данных: some_async_data
Что я делаю не так? Попробуйте сами, это не сложно.
Вы не выводите полученные данные.
Как же не вывожу, вон они выведены.
Вы выводите данные в модуле загрузки данных, а надо в функции draw.
Эм. А какая разница? Ну сделайте там вместо setTimeout
какой-нибудь аякс-вызов – будет абсолютно то же самое.
В общем, дайте код, который у вас работает без async/await и который, по вашему мнению, не будет работать с async/await. А то какой-то диалог ни о чём.
Что именно там надо сравнить? Там диффы из нескольких файлов каждый.
Можете сравнить число затронутых файлов или число функций, которые изменили сигнатуру.
Вы конкретный пример приведите: "Вот код с файберс, вот аналогичный код на async/await."
А то я сравню — а вы заявите "да нет, это ж не то совсем". Да и там конфиги затронуты, что вообще отношения не имеет.
Пожалуйста: https://habrahabr.ru/post/307288/
Там вообще целая статья со всяким разным. Неужели сложно просто два куска кода сюда вставить? Или уже сами поняли, что не правы?
Просто я вам привёл конкретный пример: для использования async-функции не нужно менять сигнатуры выше. Вы же разводите демагогию...
Потрудитесь всё же потратить 10 минут своего бесценного времени на чтение статьи. Там есть ответы на все ваши вопросы. Она не такая уж большая, но на её написание у меня ушёл не один день. Спасибо.
В одном потоке это делается через сопрограммы. node-fiber — добавляет их поддержку в ноду. Попробуйте, вам понравится :-)
Веб-воркеры тут ничем не помогут, к сожалению.
Там не надо "расставлять обёртки".
Да, точно. Изменяется только точка старта приложения и точки запуска асинхронных задач. Весь остальной код остаётся неизменным.
Ну, как по мне, так это слишком лихой поворот парадигмы, так как мозги уже давно привыкли к работе с асинхронщиной в том виде, к котором она представлена в js. А тут какая-то синхронизирующая магия, не понятно (вот честно, мне лень разбираться) как работающая. Не с проста же в V8 их не включили.
Прелесть этой магии в том, что вам и не нужно разбираться как она работает. Она просто работает как надо. Вы пишите простой и понятный синхронный код, но он не блокирует системный поток.
Ни на что не намекаю, но какой код, по-вашему, имеет меньшее количество "обёрток"?
var Fiber = require('fibers');
function sleep(ms) {
var fiber = Fiber.current;
setTimeout(function() {
fiber.run();
}, ms);
Fiber.yield();
}
Fiber(function() {
console.log('wait... ' + new Date);
sleep(1000);
console.log('ok... ' + new Date);
}).run();
console.log('back in main');
vs.
async function sleep(ms){
return await new Promise(done=>setTimeout(done, ms));
}
(async function(){
console.log('wait... ' + new Date);
await sleep(1000);
console.log('ok... ' + new Date);
}());
console.log('back in main');
Одинаково. Добавьте промежуточные вызовы. Для первого варианта ничего не изменится. Второй обрастёт конечными автоматами на каждый вызов.
Вот только у этого "простого и понятного" синхронного кода будут все недостатки многопоточного кода — неожиданно меняющиеся разделяемые переменные, следующие отсюда состояния гонки и т.п.
К примеру, ваша библиотека для реактивного программирования не сможет нормально отслеживать зависимости если не будет проверять текущий поток исполнения. То есть в мире существует как минимум три библиотеки (knockout, mobx и ваша), которые не смогут работать с файберами без переделок.
В случае моей библиотеки никакой гонки не будет. В случае файберов — да, возможно. Но ручное указание повсюду await-ов вас никак не убережёт от изменения разделяемой переменной обычной синхронной функцией, которую вы вызвали между чтением значения и записью в него.
От этого убережет разделение на слои. При хорошей архитектуре становится очевидно, какой вызов может привести к повторному входу, а какой — нет.
Проблема файберов — в том, что они все эти слои эффективно перемешивают.
Так дело тут не в "повторном входе", а в "разделяемом состоянии", которое может поменяться и без какого-либо "выхода".
Состояние, разделяемое между функциями модуля, не может поменяться без повторного входа в модуль.
Ага, если какой-нибудь сторонний модуль не решит вдруг вызвать колбэк нашего модуля синхронно, в ответ на наше к нему обращение. Буквально недавно так напоролся на событие blur в браузере, которое всплывало при удалении узла, и долго не понимал "какого фига всё ломается?".
Когда такие вещи недокументированы, либо документированы но неочевидны — это действительно может быть началом веселой отладки.
Но даже такие вещи иногда можно закрыть хорошей абстракцией. Например, при использовании mobx можно любые обработчики событий оборачивать в декоратор action, который выключит трекинг зависимостей и включит транзакцию.
Но даже такие ошибки это не сравнить с ситуацией, когда любой модуль может случайно передать управление любому другому модулю через механизм фиберов.
Справедливости ради, в v8 еще в январе (а скорее и сильно раньше) все как следует прокачали, и теперь try-catch, delete и еще 95% старых деоптимизаторов работают абсолютно без проблем, так можно перестать быть параноиком с этими операторами (сам таким был).
Очень сильная работа, респект и уважуха. С такими навыками и пониманием сопряженной работы компилятора и рантайма можно смело идти искать работу на этом поприще, если еще не. Конечно, озвученная выше критика такого подхода вполне оправдана для уровня приложений, но это не может отменить крутости решения, если бы это всё было реализовано на более низком уровне, чем либа для веба — тогда у нас был бы lua, или python, или ruby, которые нативно умеют в coroutines. Лайкнул, в общем.
Тем временем, зарелизилась восьмая версия Node.js, async теперь доступен по стандарту.
Минус одна причина писать свой велосипед.
А пока в nsynjs я могу просто писать последовательный неблокирующий останавливаемый код, а async/await вычисляются автоматически:
var fh = new textFile();
fh.open('../data/lorem.txt');
var s;
while (typeof(s = fh.readLine(synjsCtx).data) != 'undefined')
{
if(s)
console.log(s);
else
console.log("<empty line>");
wait(synjsCtx,1000);
}
fh.close();
исходник
Nsynjs – JavaScript-движок с синхронными потоками и без колбеков