Комментарии 119

Стоило ли делать все это ради неявного async/await с переусложненным интерфейсом? В современном javascript не используются коллбэки, насколько мне известно — их место заняли Promise, которые, в свою очередь, пишутся в псевдосинхронном стиле через await.

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

Казалось бы, именно ради этого и был написан babel...

А этот движок прям сразу в крупных компаниях возьмут на вооружение...

Есть такая штука как co https://github.com/tj/co
позволяет в том числе подготовить код к переходу на async await. кроме того поддерживает thunk, т.е. можно работать со старым кодом на колбэках как если бы это были промисы

В ноде – не нужен. Но в ноде и async/await уже можно (начиная с 7.6)...

В процессе более активного использования nsynjs список отличий от async/await сформировался такой:

— в 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

Event loop в nsynjs это просто цикл while(...) {...}, который выполняется в главном jS-потоке. Этот цикл выполняет свои тики пока не встретит вызов функции. Указатель на функцию анализируется на предмет типа функции и надо ли подождать, и если надо, то просто цикл останавлиавается по break. Это похоже на невытесняющую многозадачность, но все происходит внутри одного процесса JS. Поэтому вебворкеры здесь вообще ни при чем.

зачем в принципе нужно «отслеживать активные обертки»
в случае, если псевдопоток nsynjs завершается извне (типа как по SIGHUP), и надо освободить ресурсы, инача они вызовут колбеки. Стандартный JS такие возможности не предоставляет, ну тоесть надо писать учет активных функций самому. Но раз уж у нас есть свой event-loop и свои стеки, то почему бы не реализовать чтобы это делалось автоматически?
прелесть промисов в явном, детерминированном управлении event loop — см. концепции greenlets, fibers и т.д.

Нет, эти концепции не требуют никакой "явности". Вообще, странно слышать термин "явность" от человека, использующего async, который неявно превращает функцию в конечный автомат.

Какая разница, если в конечном итоге все языки это дело превращают в конечный автомат? Просто не понимаю, не ужели в c++, при компиляции в asm не получается примерно такой же конструкции при вызове системных асинхронных функций?

Не превращаются. Не получается. Почти во всех языках (кроме c# и теперь уже JS) волокна реализуются через переключение стеков, что является весьма дешёвой операцией по сравнению с кучей конечных автоматов.

И с каких пор дополнительный стек является более дешевым чем конечный автомат?

Переключение стека производится только когда в этом есть необходимость (а она возникает сравнительно редко) и заключается в простом изменении указателя на стек.


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

Которая используется сообща всеми функциями в стеке, а не так, что для каждой функции сборщиком мусора выделяется отдельный кусок памяти.

Осталось найти способ заранее определить сколько памяти понадобится этим самым всем функциям в стеке.

Здается мне, в стеке сидят только указатели для локальных переменных, ну и скорее всего примитивы с известным размером типа number или boolean, А все остальное все равно придется хранить в куче.

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

Любой массив съест у вас больше лишней памяти нежели стек.

Столько сил потрачено зря, действительно async/await чем не синхронность?
Тем, что не позволяет скрывать реализацию функции, вот была у нас функция синхронной мы писали без await, стала синхронной мы должны везде изменить на await, в итоге процентов 20 высокоуровневого кода становится заполнено ключевым словом «await».

По проекту, задумка интересна, но ИМХО, это должно решаться автоматически, т.е. компилятор видит, что функция асинхронна и всегда «подставляет» await. Если же хотим получить Promise из нее и выполнить асинхронно, пишем специальную функцию (по опыту, таких ситуаций 0.1%).
Из за этого вы решили разрабатывать этот проект? В ES6 можно использовать стрелочные функции, ушло много мешанины, и я думаю для некоторых асинхронных функций пометка async не то чтобы нагружает, наоборот помечает, что это асинхронная, что она возвращает промис. Ну не знаю, не представляю пока, где бы я ее применил бы.
это должно решаться автоматически, т.е. компилятор видит, что функция асинхронна и всегда «подставляет» awai

Согласен, именно так и должно быть. Вместо этого разработчики языка всем парят, что генераторы/промисы/async/await это круто и теперь надо всем учится программировать по-новому: массивы перебирать рекурсией, желательно хвостовой, и вообще переходить на ФП. Приходится за них это делать то, что должно было быть сделано много лет назад.
Недавно тут штудировал esdiscuss на тему do notation, которая позволила бы решить проблемы с синтаксисом при использовании монад, которые, в свою очередь, прекрасно справляются с описанной вами проблемой. Ну что уж, выводы неутешительные, конечно, хотя там сам Брендан Айк написал год назад «We'll get it»!

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

Возможно. Нужно уметь замораживать и размораживать текущий стек вызовов. Делать это можно либо через node-fibers, либо через бросание исключения + реактивное программирование.

Как вы себе представляете замораживание стека вызовов компилятором?

vintage
Ну я не соглашусь, менять runtime javascript выйдет дороже, поэтому реально выстрелил typescript, babel и flow, а не куча компиляторов с тяжеленным или не кроссплатформенным рантаймом.
mayorovp
Компилятор вполне способен выводить типы, определять нужные ветви кода из чужого модуля (tree shaking), ну и никто не запрещает ввести ограничения на такой язык (которые так и так будут полезны).
Другое дело, как это сделать красиво и явно для разработчиков, тут нужно менять и технологии, и культуру.

Что пиарят — то и выстреливает. node-fibers — вполне себе лёгкий рантайм. Лучше бы его стандартизировали, а не async/await. $mol_atom — кроссплатформенная либа кило на 10, но она больше про реактивное программирование, а синхронный код — приятный бонус.

В вашем комментарии скрыта огромная проблема — неопытность в проектировании больших проектов. Ни один человек в здравом уме не будет менять функцию/метод настолько, чтобы потребовалась асинхронщина.
В данном случае правильнее будет написать новую асинхронную фукцию, чтобы как раз, не ломать совместимость с предыдущим кодом. Введение этого «движка» в проект только ухудшит поддержку кода, по нескольким причинам:
— Новый слой абстракции. Это всегда проблема, когда этот слой не нужен.
— Неизвестная технология. Как новичкам, так и старичкам в проекте придется разбираться в работе этого модуля, придется исследовать баги, ждать пока Вы их поправите, или же править их самому.
— Не стандарт. Этого нет в ECMAScript
Что только люди не придумают… А ведь просили же, дайте нам инструмент для работы с асинхронностью. Ну дали. А воз и ныне там.

Автор, а вы видели модуль co, например?


co(function*(){
    var data = yield ajaxGetJson("data/index.json");
    for(var i in data) {
        var el = yield ajaxGetJson("data/"+data[i]);
        progressDiv.append("<div>"+el+"</div>");
        yield wait(1000);
    };
});
Да, недостатки те же, что и у async/await:
1. Если какая-то функция внизу стека стала async-await (или yield), то все вызывающие функции, и весь граф вызовов, надо также менять на async-await. Я считаю это неправильно, когда программист должен отвлекаться на такие вещи.
2. Несовместимо с некоторыми браузерами (только через babel)

Кстати, спасибо: благодаря комментариям я внезапно узнал, что node.js уже нативно поддерживает async/await и отказался от co. Смысла городить велосипеды я не вижу, честно. В вашем случае всяких обёрток и прочей лишней работы приходится делать больше, чем в случае с async/await.

Вот такое решение получилось:

index.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');
});



user.js
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;
});



greeter.js
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;
});



wrappers.js
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)

Да, модули можно передавать в параметрах в синхронный код. Это так и сделано, например в файле user.js:
...
var wrappers = require('./wrappers');
nsynjs.run(synchronousCode,{},wrappers,function (m) {
    module.exports = m;
});

Можно и так, и так, кому как больше нравится.
В нативном require в node модули можно тоже грузить несколькими способами.

Нужно всё же такое решение, которое позволит по минимуму отступать от обычного кода.

По-сравнению с Babel он:
  • исполняется значительно быстрее,

Тем, кто просто ищет решение, способное выполняться быстрее чем Babel, могу порекомендовать попробовать tsc (Typescript Compiler).

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

Ставить в достоинства маленький размер по сравнению с 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 раза не писать рендеринг, но принципиальных препядствий этому я не вижу.
Вы в своем уме? Вы хотите переписать пол-инфраструктуры, дебаггер и кучу сопровождающих вещей, которые по природе своей асинхронны и писались под асинхронную среду с event loop'ом только для того, чтобы переть поперек паровоза изначального асинхронного дизайна, который не просто так был выбран? Бога ради, зачем вам js? Пишите лучше на чем-нибудь более синхронном.

"асинхронный дизайн" — самая большая ошибка дизайна JS. Сейчас её пытаются худо бедно исправить через async-await.

Ну, «дизайн JS» вообще звучит как-то нелепо, наверное я погорячился :)

Но почему худо бедно? Не вижу в async/await как-то очевидных проблем. Сразу скажу, я не испытываю дискомфорта, когда явно видно, асинхронная функция или нет.

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

Ну, казалось бы, эффект такой же, как если поменять сигнатуру функции. Тут в помощь TS и компания.

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

Ну так если вы хотите поднять обработку этого эффекта (асинхронщины) выше по стэку, вам придется научить все функции работе с этим эффектом. В противном случае он просто затеряется, как это бывает с промисами посреди функции.

Edit: Другое дело, что компилятор (TS по крайней мере) это никак не поймает…

Так и и не хочу его никуда "поднимать". Я хочу загрузить файл синхронно, но не блокируя интерфейс.

Как вы это сделаете в одном потоке? Не нужно гнать на беднягу JS из-за того, что ему не дается доступ к нескольким потокам.

Ну и никто не отменял веб-воркеры для таких задач.

Кажется, 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."


А то я сравню — а вы заявите "да нет, это ж не то совсем". Да и там конфиги затронуты, что вообще отношения не имеет.

Там вообще целая статья со всяким разным. Неужели сложно просто два куска кода сюда вставить? Или уже сами поняли, что не правы?


Просто я вам привёл конкретный пример: для использования 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, который выключит трекинг зависимостей и включит транзакцию.


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

А что там не так с этой ситуацией? Всё отлично работает. Ещё ни разу не встречал проблем с этим.

Не поленился и дочитал статью. Сложилось впечатление, что автор ранее никогда не писал на js, и пришёл из мира системных языков. Думаю, что именно оттуда такая тяга к последовательному исполнению кода.

Посмотрел в исходный код, это просто ужас:

* Один здоровенный файл на over2000 строк, никакой модульности.
* Только 1 комментарий на всю тонну кода (в конце, может и в середине есть, но это не точно).
* Расширение прототипа стандартного объекта Array, а это антипаттерн.
* Вообще нет ни единого стиля кода, ни линтеров.
* Весь кода написан в ES5, когда на дворе 2017.
* Нет и намёка на оптимизации — операторы delete, не вынесенные try-catch и прочее.
* Странные и непонятные тесты.

Хотел посмотреть, что делает этот модуль под капотом, но после взгляда на сорцы пропало всякое желание с ним связываться.
> Нет и намёка на оптимизации — операторы delete, не вынесенные try-catch и прочее.
Справедливости ради, в v8 еще в январе (а скорее и сильно раньше) все как следует прокачали, и теперь try-catch, delete и еще 95% старых деоптимизаторов работают абсолютно без проблем, так можно перестать быть параноиком с этими операторами (сам таким был).

Эти операторы прокачаны в turbofan, новый оптимизирующий компилятор в V8, он будет включён по дефолту в node@8. Но опять же, если нужна поддержка старых версий браузеров и node, то параноить придётся ещё долго.

самая большая проблема с такими библиотеками — в дальнейшем код будет сложно перевести на те технологии которые уже скоро будут базовыми. сила babel ещё и в том что потом нужно будет просто компилятор отключить и все будет работать
Осторожно, далее нет сарказма, возможен вывих чувства юмора.
Очень сильная работа, респект и уважуха. С такими навыками и пониманием сопряженной работы компилятора и рантайма можно смело идти искать работу на этом поприще, если еще не. Конечно, озвученная выше критика такого подхода вполне оправдана для уровня приложений, но это не может отменить крутости решения, если бы это всё было реализовано на более низком уровне, чем либа для веба — тогда у нас был бы lua, или python, или ruby, которые нативно умеют в coroutines. Лайкнул, в общем.

Тем временем, зарелизилась восьмая версия Node.js, async теперь доступен по стандарту.


Минус одна причина писать свой велосипед.

Когда разработчики JS уберут ключевые слова async/await, чтобы было так же как и во всех остальных языках (естественно, чтобы это не создавало блокировок event-loop), тогда этот велосипед будет не нужен.

А пока в 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();
исходник
Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.