Считается, что мир JavaScript бурно развивается: регулярно выходят новые стандарты языка, появляются новые синтаксические фишки, а разработчики моментально все это адаптируют и переписывают свои фреймворки, библиотеки и прочие проекты с тем, чтобы все это использовалось. Сейчас, например, если вы всё ещё пишете в коде var, а не const или let, то это уже вроде как моветон. А уж если функция описана не через стрелочный синтаксис, то вообще позор…

Однако, все эти const-ы, let-ы, class-ы и большинство других нововведений не более чем косметика, которая хоть и делает код красивее, но действительно острых проблем не решает.

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

Чем хороши callbacks?


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

Чем плохи callbacks?


Первое, с чем обычно сталкивается новичок, это тот факт, что с ростом сложности код быстро превращается в малопонятные многократно вложенные блоки — «callback hell»:

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

Во-вторых, если функции с колбеками соединены друг с другом логикой, то эту логику приходится дробить и выносить в отдельные именованные функции или модули. Например, код выше выполнит цикл «for» и запустит множество fetch(array_of_urls[i]... мгновенно, и если array_of_urls слишком большой, то движок JavaScript зависнет и/или упадет с ошибкой.

С этим можно бороться путем переписывания цикла «for» в рекурсивную функцию с колбеком, но рекурсия может переполнить стек и также уронить движок. Кроме того, рекурсивные программы труднее для понимания.

Другие пути решения требуют использования дополнительных инструментов или библиотек:

  • Promises – позволяет писать код колбеков внутри неких объектов. В результате это те же колбеки, но меньшей вложенности и соединенные друг с другом в цепочки:

    firstMethod().then(secondMethod).then(thirdMethod);

    На мой взгляд Promises это костыль, потому что

    1. цепочки вызывают функции только в одном заданном порядке,
    2. если порядок может менятся в соответсвии с какой-то логикой, по-прежнему приходится дробить логику в колбеках на отдельные функции,
    3. для кодирования логики между функциями по-прежнему приходится что-то изобретать, вместо того, чтобы просто пользоваться стандартными операторами if, for, while и т.п.
    4. логика с Promises выглядит малопонятно.

  • async (библиотека) — позволяет объявить массив функций с колбеками, и исполнять их одну за другой, или одновременно. Недостатки те же, что и у Promises.
  • async/await – новая возможность в JavaScript, основанная на generators, позволяет останавливать и возобновлять исполнение функции.

Будущее, судя по всему, за async/await, но пока это будущее не наступило, и многие движки эту возможность не поддерживают.

Чтобы иметь возможность исполнять код с async/await на актуальных на данный момент движках JavaScript 2015, были созданы транспиляторы — преобразователи кода из нового JavaScript в старый. Самый известный из них, Babel, позволяет конвертировать код Javascript 2017 с async/await в JavaScript 2015 и запускать его на практически всех используемых в данный момент движках.

Выглядит это примерно так:

Исходный код на JavaScript 2017:

async function notifyUserFriends(user_id) {
  var friends = await getUserFriends(user_id);

  for(var i=0; i<friends.length; i++) {
    friend = await getUser(friends[i].id);
    var sent = await sendEmail(freind.email,"subject","body");
  }
}

Конвертированный код на JavaScript 2015:

Cпрятано в спойлер
"use strict";

var notifyUserFriends = function () {
  var _ref = _asyncToGenerator(regeneratorRuntime.mark(function _callee(user_id) {
    var friends, i, sent;
    return regeneratorRuntime.wrap(function _callee$(_context) {
      while (1) {
        switch (_context.prev = _context.next) {
          case 0:
            _context.next = 2;
            return getUserFriends(user_id);

          case 2:
            friends = _context.sent;
            i = 0;

          case 4:
            if (!(i < friends.length)) {
              _context.next = 14;
              break;
            }

            _context.next = 7;
            return getUser(friends[i].id);

          case 7:
            friend = _context.sent;
            _context.next = 10;
            return sendEmail(freind.email, "subject", "body");

          case 10:
            sent = _context.sent;

          case 11:
            i++;
            _context.next = 4;
            break;

          case 14:
          case "end":
            return _context.stop();
        }
      }
    }, _callee, this);
  }));

  return function notifyUserFriends(_x) {
    return _ref.apply(this, arguments);
  };
}();

function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }


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

Всё это само по себе требует нетривиальных усилий. Кроме того, Babel тянет за собой около 100 кб минифицированного кода «babel-polyfill», а сконвертированный код работает медленно (на что косвенно намекают многочисленные конструкции case номер_строки в сгенерированном коде).

Посмотрев на все это, я решил написать свой велосипед — SynJS. Он позволяет писать и синхронно исполнять код с колбеками:

function myTestFunction1(paramA,paramB) {
    var res, i = 0;
    while (i < 5) {
        setTimeout(function () {
            res = 'i=' + i;
            SynJS.resume(_synjsContext); // < –- функция для сигнализации, что колбек закончен
        }, 1000);
        SynJS.wait(); // < – оператор, останавливающий исполнение
        console.log(res, new Date());
        i++;
    }
    return "myTestFunction1 finished";
}

Исполнить функцию можно следующим образом:

SynJS.run(myTestFunction1,null, function (ret) {
    console.log('done all:', ret);
});

Результат будет такой:

i=0 Wed Dec 21 2016 11:45:33 GMT-0700 (Mountain Standard Time)
i=1 Wed Dec 21 2016 11:45:34 GMT-0700 (Mountain Standard Time)
i=2 Wed Dec 21 2016 11:45:35 GMT-0700 (Mountain Standard Time)
i=3 Wed Dec 21 2016 11:45:36 GMT-0700 (Mountain Standard Time)
i=4 Wed Dec 21 2016 11:45:37 GMT-0700 (Mountain Standard Time)

По-сравнению с Babel он:

  • легче (35кб без минимизации),
  • не имеет зависимостей,
  • не требует компиляции,
  • исполняется примерно в 40 раз быстрее (хотя это может быть не так критично при работе с медленными функциями).

SynJS берет указатель на функцию в качестве параметра, парсит эту функцию на отдельные операторы (парсит вложенные операторы рекурсивно, если необходимо), оборачивает их все в функции, и помещает эти функции в древовидную структуру, эквивалентную коду функции. Затем создается контекст исполнения, в котором хранится локальные переменные, параметры, текущее состояние стека, программные счётчики и другая информация, необходимая для остановки и продолжения выполнения. После этого операторы в древовидной структуре исполняются один за другим, используя контекст в качестве хранилища данных.

Функция может быть выполнена через SynJS следующим образом:

SynJS.run(funcPtr,obj, param1, param2 [, more params],callback)

Параметры:

— funcPtr: указатель на функцию, которую надо выполнит синхронно
— obj: объект, который будет доступен в функции через this
— param1, param2: параметры
— callback: функция, которая будет выполнена по завершении

Чтобы можно было дожидаться завершения колбека в SynJS существует оператор SynJS.wait(), который позволяет остановить исполнение функции, запущенной через SynJS.run(). Оператор может принимать 3 формы:

— SynJS.wait() — останавливает исполнение пока не будет вызван SynJS.resume()
— SynJS.wait(number_of_milliseconds) – приостанавливает исполнение на время number_of_milliseconds
— SynJS.wait(some_non_numeric_expr) – проверяет (!!some_non_numeric_expr), и останавливает исполнение в случае false.

С помощью SynJS.wait можно ожидать завершения одного или нескольких колбеков:

        var cb1, cb2;
        setTimeout(function () {
            cb1 = true;
            SynJS.resume(_synjsContext);
        }, 1000);
        setTimeout(function () {
            cb2 = true;
            SynJS.resume(_synjsContext);
        }, 2000);
        SynJS.wait(cb1 && cb2);

Чтобы дать сигнал о завершении колбека в основной поток используется функция

SynJS.resume(context)

Обязательный параметр context содержит ссылку на контекст исполнения, который необходимо уведомить (так как каждый вызов SynJS.run создает и запускает отдельный контекст, в системе может существовать одновременно несколько запущенных контекстов).

При парсинге SynJS оборачивает каждый оператор оборачивается в функцию следующим образом:

function(_synjsContext) {
	... код оператора ...
}

Таким образом можно использовать параметр _synjsContext в коде колбека для сигнализации о завершении:

SynJS.resume(_synjsContext);

Обработка локальных переменных.


При парсинге тела функции SynJS определяет декларации локальных переменных по ключевому слову var, и создаёт для них хеш в контексте исполнения. При обёртывании в функцию код оператора модифицируется, и все ссылки на локальные переменные заменяются ссылками на хеш в контексте исполнения.

Например, если исходный оператор в теле функции выглядел так:
	var i, res;
	...
    setTimeout(function() {
        res = 'i='+i;
        SynJS.resume(_synjsContext);
    },1000);

то оператор, обернутый в функцию будет выглядеть так:

function(_synjsContext) {
    setTimeout(function() {
         _synjsContext.localVars.res = 'i='+_synjsContext.localVars.i;
         SynJS.resume(_synjsContext);
    },1000);
}

Несколько примеров использования SynJS

1. Выбрать из БД массив родительских записей, для каждой из них получить список детей.

2. По списку URL-ов, получать их один за другим, пока содержимое URL-а не будет удовлетворять условию.

код
	var SynJS = require('synjs');
	var fetchUrl = require('fetch').fetchUrl;
	
	function fetch(context,url) {
		console.log('fetching started:', url);
		var result = {};
		fetchUrl(url, function(error, meta, body){
			result.done = true;
			result.body = body;
			result.finalUrl = meta.finalUrl; 
			console.log('fetching finished:', url);
		    SynJS.resume(context);
		} );
		
		return result;
	}

	function myFetches(modules, urls) {
		for(var i=0; i<urls.length; i++) {
			var res = modules.fetch(_synjsContext, urls[i]);
			SynJS.wait(res.done);
			if(res.finalUrl.indexOf('github')>=0) {
				console.log('found correct one!', urls[i]);
				break;
			}
		}
	};
	
	var modules = {
			SynJS: 	SynJS,
			fetch:	fetch,
	};
	
	const urls = [
	              'http://www.google.com', 
	              'http://www.yahoo.com', 
	              'http://www.github.com', // This is the valid one
	              'http://www.wikipedia.com'
	          ];
	
	SynJS.run(myFetches,null,modules,urls,function () {
	    console.log('done');
	});


3. В базе данных, обойти всех детей, внуков и т.д. некоторого родителя.

Код
	global.SynJS = global.SynJS || require('synjs');
	var mysql      = require('mysql');
	var connection = mysql.createConnection({
	  host     : 'localhost',
	  user     : 'tracker',
	  password : 'tracker123',
	  database : 'tracker'
	});

	function mysqlQueryWrapper(modules,context,query, params){
		var res={};
		modules.connection.query(query,params,function(err, rows, fields){
			if(err) throw err;
			res.rows = rows;
			res.done = true;
			SynJS.resume(context);
		})
		return res;
	}
	
	function getChildsWrapper(modules, context, doc_id, children) {
		var res={};
		SynJS.run(modules.getChilds,null,modules,doc_id, children, function (ret) {
			res.result = ret;
			res.done = true;
		    SynJS.resume(context);
		});
		return res;
	}
	
	function getChilds(modules, doc_id, children) {
		var ret={};
		console.log('processing getChilds:',doc_id,SynJS.states);
		var docRec = modules.mysqlQueryWrapper(modules,_synjsContext,"select * from docs where id=?",[doc_id]);
		SynJS.wait(docRec.done);
		ret.curr = docRec.rows[0];
		
		ret.childs = [];
		var docLinks = modules.mysqlQueryWrapper(modules,_synjsContext,"select * from doc_links where doc_id=?",[doc_id]);
		SynJS.wait(docLinks.done);

		for(var i=0; docLinks.rows && i < docLinks.rows.length; i++) {
			var currDocId = docLinks.rows[i].child_id;
			if(currDocId) {
				console.log('synjs run getChilds start');
				var child = modules.getChildsWrapper(modules,_synjsContext,currDocId,children);
				SynJS.wait(child.done);
				children[child.result.curr.name] = child.result.curr.name;
			}
		}
		return ret;
	};
	
	
	var modules = {
			SynJS: 	SynJS,
			mysqlQueryWrapper: mysqlQueryWrapper,
			connection: connection,
			getChilds: getChilds,
			getChildsWrapper: getChildsWrapper,
	};
	
	var children={};
	SynJS.run(getChilds,null,modules,12,children,function (ret) {
	    connection.end();
	    console.log('done',children);
	});


На данный момент я использую SynJS для написания браузерных тестов, в которых требуется имитировать сложные пользовательские сценарии (кликнуть ”New”, заполнить форму, кликнуть ”Save”, подождать, проверить через API что записалось, и т. п.) — SynJS позволяет сократить код, и самое главное, повысить его понятность.

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

> Проект на гитхабе
> NPM

P.S. Чуть не забыл, в SynJS имеется оператор SynJS.goto(). А почему бы и нет?
Поделиться с друзьями
-->

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


  1. alQlagin
    08.01.2017 09:03
    +7

    Судя по ридми на 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


    1. amaksr
      08.01.2017 09:20
      -1

      try...catch недоступно только в теле самой вызываемой функции, которая запускается через SynJS.run (myTestFunction1 в статье). В любых других функциях, в том числе вызываемых из myTestFunction1 try...catch доступен.

      Кстати, так вами любимый async/await тоже использует Promise
      да, но async/await стал возможен не благодаря Promises, а благодаря генераторам, которые нативно позволяют останавливать/возобновлять выполнение контекста в движке. Чтобы получить Promises достаточно подключить небольшой полифил, а вот чтобы получить остановку/возобновление контекста исполнения в ES2015 в Babel фактически пришлось создать State machine, парсить и эмулировать исполнение операторов функции примерно так же, как это делает SynJS.


      1. staticlab
        08.01.2017 15:52
        +3

        Но ведь это в ES2015, в то время как async-await уже доступны нативно в Chrome 55, ожидаются в FF 52, находятся в разработке в Edge, Safari и Node.js. Суть в том, что async-await стандартизованы, и они скоро появятся во всех браузерах, а SynJS придётся тащить за собой.


        1. Finom
          08.01.2017 18:40
          +1

          На ноде уже есть async/await.


          1. staticlab
            08.01.2017 22:02

            Он пока что под --harmony и не рекомендуется, поскольку там есть баг с утечкой памяти. Собственно, поэтому я и отнёс ноду в раздел «в разработке».


        1. amaksr
          08.01.2017 19:57
          -3

          IE ближайшие пару лет никуда не денется, особенно из западных компаний, у которых обычно есть policies не обновлять ПО до самых последних версий.


          1. amaksr
            09.01.2017 00:13
            -1

            Интересно, как любое упоминание IE моментально набирает минусы. Но вот я сейчас работаю с 2-мя западными компаниями, которые сидят на Windows 7 и IE 9, и обновлений на Edge даже и не планируют пока. Они совместимость со своим старьем всегда ставят в условия ТЗ. Не отказывать же им…


            1. taliban
              10.01.2017 01:52
              +1

              Отказывать, внезапно.
              http://bluebirdjs.com/ зацените штуку, работает начиная с ие7 весит меньше чем ваша библиотека, полностью совместима с promise, проверена временем, и не имеет недостатков которые у вас пока еще есть.


            1. kk86
              13.01.2017 02:56

              Как я вас понимаю!


    1. vintage
      08.01.2017 09:20
      +3

      плюс похоже будет проблема с замкнутыми переменными вне SynJS.run


      1. amaksr
        08.01.2017 09:40

        Функция, вызываемая через 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.


  1. amaksr
    08.01.2017 09:19

    del


  1. alQlagin
    08.01.2017 09:33

    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, простите за каламбур


    1. amaksr
      08.01.2017 09:54

      Если в myFetches есть return, то его результат будет параметром колбека:

      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 вызывается рекурсивно чтобы обойти дерево.


      1. alQlagin
        08.01.2017 10:57

        В 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 так?


        1. amaksr
          08.01.2017 11:24

          ну то есть от callback hell мы никуда не ушли?

          Мы ушли от callback-hell только внутри функции, вызываемой через SynJS.run. Все остальные функции подчиняются тем же законам JavaScript, что и раньше. Точно так же в случае async/await мы должны объявить функцию через async, если мы собираемся в ней ждать коллбеки (ну и плюс еще сделать оболочки с Promises для функций с колбеками, которые мы собираемся вызывать).

          Вообще этот момент мне более всего непонятен: почему нельзя было ввести в JavaScript оператор, который бы приостанавливал исполнение контекста без блокировки других контекстов, лет так 10 назад? Тогда никто и не знал бы сейчас про callback hell. Почему только недавно такая возможность появилась, но и то в виде генераторов? Выглядит так, что кто-то сильно ошибся с дизайном когда-то давно, поэтому мы сейчас и имеем все эти костыли.


          1. vintage
            08.01.2017 12:12

            В NodeJS эта возможность появилась 6 лет назад.. А не стандартизировали это потому, что дизайном языка занимаются не грамотные архитекторы, а толпа леммингов.


  1. vintage
    08.01.2017 12:15

    Не хотите ли добавить SynJS в эту коллекцию асинхронных паттернов? https://github.com/nin-jin/async-js


    1. amaksr
      09.01.2017 07:42

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


      1. vintage
        09.01.2017 08:11

        Что предложите добавить, чтобы это обрело смысл?


        1. amaksr
          09.01.2017 21:00

          SynJS лучше справляетс с задачами, где перемешаны колбеки, цикли, условия и рекурсии.
          Но ваша задача натолкнула меня на мысль, что в хорошо бы добавить возможность приостанавливать не только операторы в некоторой функции, но и вычисление выражений. Тогда код, который сейчас в 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");

          Наверное попробую это реализовать…


  1. brooth
    08.01.2017 12:49
    +10

    Не пойму, почему немного не потерпеть пока async/await пойдет в масссы а пока пересидеть на babel?


    1. valiorik
      08.01.2017 15:16
      +1

      Потому что IE?
      Все бизнесы, знаковые мне изнутри, поддердживают 2+ старых версий IE, для которых писать быстрый JS очень не просто.


      1. brooth
        08.01.2017 16:13
        -1

        Я не фронтендщик, возможно чего-то не понимаю, но разве IE 2 может в ajax? Мы же тут про async говорим… Опять же, имеет ли такое значение скорость javascript-а, когда у нас тут асинхронный запрос на сервер?


        1. Danik-ik
          08.01.2017 17:29
          +1

          Мне кажется, имелось в виду "не менее двух предпоследних версий", а не археология


          1. brooth
            08.01.2017 17:56

            Да, точно. Мне аж больно за коллег стало.


  1. yogurt1
    08.01.2017 15:34
    +1

    Про async/await чистая ложь
    Во-первых, transform-async-to-generator просто заменяет все await на yield и оборачивает функцию в вызов функции co (github.com/tj/co, можно свою реализацию подставить). Также есть asynctogen, с которым нет смысла тащить babel, если других фич не используете
    Во-вторых, у вас включен regenerator, и поэтому код страшный


    1. guyfawkes
      08.01.2017 17:32

      Как без включения regenerator будет работать конвертация async/await?


      1. SuperPaintman
        10.01.2017 09:28
        +1

        regenerator преобразует генераторы в стейт-машину которая будет работать и на старых версиях V8, которые не поддерживают их.


        А async/await может спокойно без него работать, если упомянутые выше генераторы поддерживаются движком (благо они уже более распространены)


        1. guyfawkes
          10.01.2017 13:53

          Я именно про случай преобразования для движков, не поддерживающих async/await


          1. staticlab
            10.01.2017 13:57
            +1

            Вы хотели сказать "не поддерживающих генераторы"?


            1. guyfawkes
              10.01.2017 14:40

              Не совсем понимаю вас. Смотрим на сводную таблицу, к примеру, здесь:
              https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function

              Если хром будет не 55, а 54 версии, он, судя по всему, не будет поддерживать async. Как поддержка генераторов ему поможет?


              1. staticlab
                10.01.2017 14:51
                +2

                transform-async-to-generator просто заменяет все await на yield и оборачивает функцию в вызов функции co (github.com/tj/co, можно свою реализацию подставить)

                Суть в том, что async-await вначале преобразуется бабелем в генераторы, а уже затем генераторы (ES6) транспилируются в стейт-машину для регенератора. Таким образом, если целевые браузеры поддерживают генераторы нативно, то и регенератор не нужен.


                Вам это объясняли выше, но вы задали такой вопрос, как будто бы имели в виду транпиляцию async-await для движков, не поддерживающих генераторы, почему я и переспросил.


                1. guyfawkes
                  10.01.2017 15:52

                  Мы оба недопоняли друг друга :) Спасибо за пояснения.


  1. apelsyn
    08.01.2017 16:15
    +2

    Еще про генераторы забыли. Они поддерживаться уже давно.


    1. amaksr
      08.01.2017 20:36
      -1

      Генераторы поддерживаются не везде. Они необходимы чтобы нативно приостанавливать выполнение функции и реализовать ожидание чего-то (возможность приостанавливаться и ждать как раз и позволиляет избавится от колбеков), но в IE например их нет. Поэтому Babel фактически создает свою State machine, парсит код функции и исполняет ее операторы сам, без вызова этой функции напрямую.


      1. staticlab
        09.01.2017 21:40

        А вы не проверяли, что быстрее: нативные генераторы, генераторы через regenerator или ваш велосипед?


        1. amaksr
          09.01.2017 22:30

          Я тестировал вот этой програмкой:

          спойлер
          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);
          })
          
          


          1. vintage
            09.01.2017 23:05

            Зачем в этой абсолютно синхронной программе вообще использовать SynJS?


            1. amaksr
              09.01.2017 23:37

              Все медленные функции были убраны специально для оценки быстродействия самого SynJS


              1. vintage
                10.01.2017 00:39
                +1

                В результате SynJS не делает ровным счётом ничего.


                1. amaksr
                  10.01.2017 01:11

                  Ну да. А сгенерированный Babel-ем код тоже не делает ничего, но только медленно.


                  1. vintage
                    10.01.2017 01:25
                    +1

                    Нет, он много чего делает. Пусть и впустую. Зачем вы вставляете async и await если функции синхронные?


          1. yogurt1
            11.01.2017 16:06
            +1

            "Нативные" промисы очень медленные. Надо брать Bluebird


  1. KlonD90
    08.01.2017 16:24

    Ко всему прочему хотел бы докинуть что у вас же вышли старые добрые fiber'ы и они уже давно есть в NodeJS как одно из решений но от них отказываются. Как-то генераторы надежнее выглядят и сводятся к тому же.


  1. 3al
    08.01.2017 16:33

    Но это же не решение. Суть callback hell — в том, что с ним сложно обрабатывать ошибки читаемым способом. Этот велосипед не улучшает вообще ничего.


  1. feverqwe
    08.01.2017 16:39
    +1

    Что написали велосипед — хорошо, возможно что то поняли. Но не надо это использовать нигде, хотя бы потому, что
    оно парсит код. От этого мало того, что отваливается вся оптимизация, которые делает браузер, но и уж точно вы не учли все возможные виды написания кода, который уж точно не уместится в 34kb.
    Просто используйте Promise, и не нужно мучить ни себя не других. Если кто то внезапно столкнется с проблемами, которые вызывает ваш велосипед — цена переписывания кода с синхронного с костылями на асинхронный будет слишком высока, проще сразу писать нормально.

    Да и полифил promise не минифицированный занимает всего 8kb, а уменьшенный 3kb.


    1. amaksr
      08.01.2017 20:25
      -1

      Распарсенные операторы в SynJS компилируются в функции через eval (насколько я знаю Node тоже загружает файлы через eval). Поэтому все оптимизации, происходящие в eval никуда не отвалятся. К тому же делается это только 1 раз при первом вызове функции.

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


  1. titov_andrei
    08.01.2017 17:03

    Заказчику разве не всё равно, какой дрелью ему отверстие в стене будут делать?


    1. feverqwe
      08.01.2017 17:12

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


  1. babylon
    08.01.2017 19:06
    +1

    Редкий случай когда тезисы статьи не вызывают у меня отторжения.


  1. rumkin
    08.01.2017 19:11

    Велосипед не засчитан, так как не работает в ряде случаев. Вариант с парсингом затирает контекст исполнения, т.о. я не могу быть уверен в том, что функция полученная откуда-то будет работать предсказуемым образом.


    1. amaksr
      08.01.2017 20:06

  1. Rulexec
    08.01.2017 20:02

    Выглядит так, будто бы оно просто распарсивает функцию и делает из неё генератор.

    Почему они с тем же успехом не могли распарсивать функцию и впиливать поддержку await/async? Если это так хочется делать в рантайме, а не транспилировать.


  1. token
    08.01.2017 20:30

    Парсер классный, аж волосы зашевелились )


    1. amaksr
      08.01.2017 23:42

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


      1. token
        09.01.2017 00:48

        Простой? Посмотрите как выглядит нормально и понятно написанный парсер рекурсивного спуска: https://github.com/angrycoding/javascript-parser/blob/master/src/parser/Parser.js


      1. token
        09.01.2017 00:53

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


  1. token
    08.01.2017 20:33

    А так да, велосипед тот ещё, во — первых рантайм, а во — вторых потеря контекста.


  1. faiwer
    08.01.2017 21:15

    Какое уродливое не-решение никаких проблем. Не знаю зачем вы его придумали, и для чего это может пригодиться. Возьмите уж тогда хотя бы promise-hell, всяко менее вырвиглазнее, да и без runtime парсинга js-а.
    Но вот одну интересную мысль из топика я таки выцепил. Вы упомянули о том, что babel-polyfil весит под 100 KiB. Я с возмущением и словами "да не может быть, какая ерунда" полез смотреть и опешил. 97 KiB. Гхм. Грусть-печаль меня охватила.


  1. Jack38b
    08.01.2017 21:19

    По-моему здесь есть неплохое решение:

    Заголовок спойлера
    function processInOrder(arr, cb){
    if( arr.length > 0 ){
    arr[0].then(function(result){
    cb(result);
    processInOrder(arr.slice(1), cb);
    });
    }
    }

    var deferreds = [];
    for(var i=0; i<4; i++){
    deferreds.push( asyncRequest(i) );
    }

    processInOrder(deferreds, display);


    1. Jack38b
      08.01.2017 21:24

      Упс. Ссылка потерялась:
      http://stackoverflow.com/questions/34186336/how-to-process-the-results-of-a-series-of-ajax-requests-sequentially


      1. vintage
        08.01.2017 21:32

        Сравните с этим:


        let action = new Atom( 'action' , ()=> {
            let responses = []
            for( let i = 0 ; i < 4 ; ++i ) responses.push( asyncRequest( i ) )
            responses.forEach( display )
        } )
        action.value()


        1. Jack38b
          08.01.2017 21:50

          Врядле я бы стал это использовать. Мой код элементарно не пройдет pull request если я начну 3-rd party библиотеки/фреймфорки использвоать. Но выглядит конечно красиво. Цикл вместо рекурсии (оч. хорошо — легче оргазизовать scalability — хотя это чаще на бэкенде нужно но все же). Кстати, а эти все Atom — это не есть то же самое, что и observer pattern? CanJS такие вещи по-моему делает, и приводит это к тому, что садится performance.


          1. vintage
            08.01.2017 21:56

            Не очень верится, что вы не используете ни одной сторонней библиотеки.


            1. Jack38b
              08.01.2017 22:02

              Конечно используем. Но в них нет Atom


              1. vintage
                08.01.2017 22:24

                Что же мешает его добавить?


                1. Jack38b
                  08.01.2017 22:26

                  Выше я вопрос задал, повторю: «Atom — это не есть то же самое, что и observer pattern? CanJS такие вещи по-моему делает (там есть Observables), и приводит это к тому, что садится performance.»


                  1. vintage
                    08.01.2017 23:10

                    Это скорее computed паттерн. Есть он много где, но зачастую реализация не эффективна. Оцените масштаб трагедии.


                    1. Jack38b
                      08.01.2017 23:23

                      «это » — это вы про что? Про Атом или про CanJS? В CanJS «Observables are the subjects in the observer pattern.». А вот что такое Атом я не знаю.
                      В любом случае, я даже синтаксис вашего кода не понимаю, чтобы оценить всю его красоту. Я на javascript работаю всего год, и не знаю что такое, скажем, "()=>" (чем то похоже на C# лямбду). В целом мне кажется, что call back hell проблема состоит из двух частей — если работаешь с legacy code в большой кампании, и там много всего уже написано. А если разрабатываешь с нуля, то свой собственный код можно писать аккуратно, хотя конечно уйдет полгода пока привыкнешь к всем этим call back и deferred и Promise. Хотя, конечно, вынужден признать, что разработка на JS действительно challenging.


                      1. vintage
                        08.01.2017 23:42

                        Про атомы. Тут про них подробнее. Впрочем, CanJS тоже поддерживает "computed".


                        Да, ()=> — это та же лямбда. То же что и function(){ ... }, только сохраняет this.


                        Аккуратно писать на колбэках крайне сложно.


                        1. Jack38b
                          08.01.2017 23:46

                          Понятно. Согласен


                        1. Jack38b
                          08.01.2017 23:50

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


                          1. vintage
                            09.01.2017 00:07

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


                            1. Jack38b
                              09.01.2017 00:17

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


                              1. vintage
                                09.01.2017 00:23
                                +1

                                Я же написал или.


                                1. Jack38b
                                  09.01.2017 00:30

                                  Ясно. Попробую вставить ваш код снипет в свой проект… Удивлюсь если сработает, но кто знает :) Спасибо


                                1. Jack38b
                                  09.01.2017 00:32

                                  Сработало!


                                1. Jack38b
                                  09.01.2017 02:32

                                  А вас можно пользуясь случаем спросить — что предпочтительнее использовать при работе с Git? Я использую с командной строки или из sublime, но мне это кажется крайне неэффективным (я люблю конечно, командную строку, но не merge же в ней делать!) к тому же, часто приходится искать чей то commit и приходится для этого использовать комбинацию tools (Visual Studio + Sublime + command line). Заранее благодарен!


                                  1. vintage
                                    09.01.2017 08:13

                                    TortoiseGit, например.


                                    1. Jack38b
                                      13.01.2017 18:49

                                      Спасибо большое. Есть у меня еще один вопрос, если у вас есть время ответить буду жутко признателен.
                                      Я ищу вот какую вещь: имея удаленную git master branch, я хотел бы запускать какой нибудь скрипт (предпочтительно перл + git command) чтобы на выходе получить текстовый файл с набором diffs по каждому commit определенного пользователя.
                                      В свое время я смог достаточно быстро сделать нечто подобное для ClearCase (это как Git только от айбиэм) даже не будучи знакомым с ClearCase вообще. Но когда попробовал сделать тоже самое в git у меня не получилось, хотя я потратил существенно больше времени. Если у вас есть ссылка на нечто подобное или совет — буду крайне признателен.
                                      Большое спасибо!


                                      1. vintage
                                        13.01.2017 20:22

                                        1. Jack38b
                                          13.01.2017 20:29

                                          Спасибо! Супер. Работает как часы.


                                        1. Jack38b
                                          14.01.2017 18:44

                                          Возможно я злоупотребляю, но есть еще вопрос :)
                                          Я чаще работаю с чужим JS кодом. Мне часто приходится находить код- обработчик события. Я написал собственный скрипт, который в консоли выдает всех подписчиков на событие (jquery) по елементу, на который я ткнул мышкой ($0 в chrome), потом я по одному их всех перебираю и в среднем у меня уходит примерно час чтобы найти call back обработчик, скажем, нажатия мыши. Причем мозги после этого выпарены.
                                          Вопрос вот в чем — если способ находить это как то быстрее? И еще, я подозреваю, что опытные разработчики не занимаются поиском обработчиков перебором колбэков которые зарегистрированы на event. Может воркфлоу должен быть каким то иным? Я просто действую так, как привык — мой предыдущий опыт разработки в GUI был для деск топ где такого рода вещи делаются за несколько секунд, даже не минут. Я догадываюсь что что то я делаю не так, но не могу понять что именно. Буду признателен за совет :)


                                          1. vintage
                                            14.01.2017 20:02

                                            Для вопросов есть специальный ресурс: https://toster.ru
                                            А так, в Chrome Dev Tools можно смотреть обработчики повешенные на выделенный элемент и всех его предков.


                                            1. Jack38b
                                              14.01.2017 20:22

                                              Про Dev Tools event listeners я знал, но я думаю, это не то же самое, что обратотчики jQuery (я могу чего то не понимать).Про ресурс спасибо, вопросы буду задавать там.


                                              1. vintage
                                                14.01.2017 22:42

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


                                                1. Jack38b
                                                  14.01.2017 23:06

                                                  Спасибо большое. Но опять интересно. Какими scope? Может ссылку кините в виде исключения перед тем как я пойду задавать вопрос на тостере?

                                                  До этого я вот этим пытался пользоваться (то о чем я выше говорил):
                                                  http://stackoverflow.com/questions/570960/how-to-debug-javascript-jquery-event-bindings-with-firebug-or-similar-tool

                                                  с некоторыми модификациями. Получается не слишком хорошо — вылетает обычно несколько десятков обработчиков, в итоге приходится ставить брейкпоинт внутри jquery и уже оттуда я нахожу то, что некое CanJS-овское подобие Object.defineProperty. Devtools event listeners вообще ничего мне не дали в принципе. В итоге сейчас я просто сижу в отладчике чтобы найти то, что мне надо или пытаюсь искать по комитам в git если кто то делал что то подобное и пытаюсь оттуда посмотреть.


                                                  1. vintage
                                                    15.01.2017 00:53

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


                                                    1. Jack38b
                                                      15.01.2017 01:13

                                                      спасибо


                                      1. Jack38b
                                        13.01.2017 20:28

                                        Т.е. я понимаю, что надо слать комманду
                                        git --no-pager diff <commit^> в цикле, но проблема с merges — я хочу чтобы без них было. А я их получаю с помощью
                                        git --no-pager log и приходит много мусора.


                    1. Jack38b
                      08.01.2017 23:27

                      Cпасибо за ссылку на масштаб трагедии, полезная штучка :)


    1. amaksr
      08.01.2017 21:52

      Это решение для очень простой проблемы, так как все итерации однотипные, и не связаны никакой логикой. Такую проблему можно решать легко и через рекурсию, и через Promises, и через async.js. Все становится гораздо сложнее если вам надо делать циклы или условия в каждой итерации, и по их результатам вызывать другие асинхронные функции и ждать колбеков от них


      1. Jack38b
        08.01.2017 22:09

        Верно, но сравнивать нужно сравнимые вещи. В приведенном в статье сниппете решается та же проблема что и в приведенном мною. Если будут другие — более сложные — то и решения будут выглядеть иначе.
        На самом деле мне встречались типовые design patterns на javascript с использованием promises которые решают многие типовые проблемы — последовательная обработка только один из них.
        Ну и потом, при разработке GUI на javascript последовательная обработка не является слишком частым делом — насколько я понимаю предлагаемое решение нацелено именно на решение этой проблемы.
        Мне кажется разработчику вроде меня было бы трудно убедить коллег в использовании того, что вы создали для решение проблемы callback hell.


  1. velvetcat
    08.01.2017 21:40

    Коллеги, подскажите ненастоящему JS-нику, а способ решения проблемы с callback hell с помощью отказа от инлайнинга функций — годный/в стиле JS?

    Бонусом к этому способу идет соблюдение SRP, нормальное юнит-тестирование и все такое.


    1. staticlab
      08.01.2017 22:29

      К сожалению, часто бывает нужно замыкание.


  1. RubaXa
    08.01.2017 22:03
    +1

    На данный момент я использую SynJS для написания браузерных тестов

    Т.е. там, где даже мегабайты «лишнего» кода некритичны, да ещё и не продакшен это.


    Ну и даже не заглядывая в исходники ставлю на полную неработоспособность при использовании любого обфускатора.


    P.S. А ведь всё могло быть по другому
    Будущее, судя по всему, за async/await, но пока это будущее не наступило, и многие движки эту возможность не поддерживают.

    Кроме того, Babel тянет за собой около 100 кб минифицированного кода «babel-polyfill», а сконвертированный код работает медленно

    Посмотрев на все это, я решил написать свой велосипед — SynJS свою урезанную версию реализации async/await на голой Эсприме...


    1. amaksr
      08.01.2017 23:56

      Инструмент новый, пока обкатывается в тестах, вроде показывает себя хорошо, а значит будет и в продакшене.

      Насчет обфускатора вы скорее всего правы, но иной раз код с Promises, логикой с рекурсией, циклами и колбеками выглядит так, что никакого обфускатора и не надо.


      1. RubaXa
        09.01.2017 00:27

        Ой, ну не надо, продуманная декомпозиция решат всё.

        Забыл добавить: Но, обычно, причина callback/promise hell, банальная лень.


        1. amaksr
          09.01.2017 02:18

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


          1. TheShock
            09.01.2017 03:06
            +1

            Мы запретили на одном из крупнейших проектов брать анонимные функции вообще (трудно утечки с ними контролировать) и не было никакого колбек-хела, ибо приходилось правильно декомпозировать


      1. RubaXa
        09.01.2017 00:32
        +1

        Блин, хабр, что ты сделал.


        Насчет обфускатора вы скорее всего правы, но иной раз код с Promises, логикой с рекурсией, циклами и колбеками выглядит так, что никакого обфускатора и не надо.

        Ой, ну не надо, продуманная декомпозиция решат всё.


        Вот вы пишите про какие-то там задачи, в которых Callback/Promise страшны, но в статье их не приводите, а показываете какие-то элементарные вещи.


        Потом идет пример на async/await, дальше пугаем babel-polyfill (который содержит поддержку генераторов, итераторов и много чего), говорим что спасение есть, это SynJS и… не приводим примера той же задачи на неё, а показываем какую-то муть myTestFunction1 и c setTimeout.


        P.S. Обфускатор (он же и минификатор), сейчас используют все по умолчанию.


  1. SDSWanderer
    09.01.2017 02:03
    +4

    Серьезно? Вот это лучше промисов?
    Буду этим джунов пугать!


  1. AndreyRubankov
    11.01.2017 12:22

    Почему в каждой статье про «callback hell» всегда приводят пример самого тупого подхода к написанию кода?

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

    Я, конечно, могу ошибаться и если это так, буду рад услышать в чем именно я ошибся.


    1. faiwer
      11.01.2017 14:02
      +1

      По-моему такой подход решает проблему «callback hell» без каких-либо сторонних библиотек и без нововведений на уровне языка (async/await).

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


    1. faiwer
      11.01.2017 14:13

      Пример из живого кода
          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'));
              }
          }


      1. AndreyRubankov
        11.01.2017 15:37

        Спасибо за пример кода, это, наверно, первый адекватный пример, который я увидел, где использование async/await действительно упрощает понимание кода.
        Обычно приводят тривиальные и глупые примеры, которые даже под разряд callback hell не подходят.


      1. RubaXa
        11.01.2017 17:13

        Это какой-то, кхм, «странный» метод, где-то просто this.updateHabData, где-то await this.updateHabData, wtf?


        1. AndreyRubankov
          11.01.2017 18:52

          Там где с await будет последовательно код идти, там где без — асинхронно бесконтрольно:

          this.updateHabData(this.activeId, { habLoading: true });
          widget = await this.load(id);
          

          — порядок выполнения случайный.

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


          1. RubaXa
            11.01.2017 18:57

            Код с душком, будем честными (проверка на cached вызывает недоумение, как и в целом работа с updateHabData?!).


            1. faiwer
              11.01.2017 19:56

              А в чём собственно проблема? :) Расстановка await перед всеми вызовами updateHabData по сути ничего не поменяет. Это так, экономия на спичках (и муках отладки регенератора в случае чего), наверное избыточная, но никакой трагедии уж точно. Не думал что она кого-то смутит.


              А что не так с проверкой на cached? Она тут нужна ввиду того, что возможны два сценария:


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

              Отдельного упоминания заслуживает onsen-ий navigator.pushPage, которые занимается разного рода анимациями перелистывания экранов (ради чего и приходится городить столько кода).


              1. babylon
                11.01.2017 22:31

                Для чего два раза id передавать? Для асинхронов логично опционально передавать название события. В целом соглашусь с RubaXa.


                1. faiwer
                  12.01.2017 07:00

                  Честно говоря не понял вас :(. Что вы имеете ввиду? И про какие события идёт речь?


              1. RubaXa
                11.01.2017 22:47

                Расстановка await в произвольно порядке говорит только о том, что над кодом особо не думали и так сойдёт.


                С cached тоже самое, метод openChild умеет работать с кешем, загружать данные, показывать спиннеры, что-то обновлять (но при этом сам кеш не кладёт), наверно и кофё варить ;] Всё перемешалось в этом методе.


                1. faiwer
                  12.01.2017 06:59

                  Повторяю, await расставлены не в произвольном порядке, а в таком, что await-ятся только те операции, дождаться окончания которых критично (тут нет никаких race conditions). Повторюсь, внутри .setState от react-компоненты. Часто вы в своём коде передаёте туда callback? Думаю крайне редко, однако такие ситуации могут быть полезными. В данном случае одна такая в коде показана, т.к. state обязательно должен обновиться до того, как будет вызван onsen.navigator.pushState (иначе он упадёт не найдя нужных для render-а данных).


                  Всё перемешалось в этом методе.

                  Соглашусь только с этим. Но, увы, некоторые UI задачи требуют заморочек. А cache обновляется внутри load-а.


                  1. vintage
                    12.01.2017 09:28
                    +1

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


                    1. faiwer
                      12.01.2017 09:46

                      А вот с этим соглашусь. Серьёзный аргумент. Что с await, что с promise-ми, да даже callback-ми очень просто пустить некоторые ошибки на самотёк. В nodejs для этого даже костыль в виде uncaughtException воткнули.


        1. faiwer
          11.01.2017 19:51

          Я просто расставил await только в тех местах, где критично дождаться окончания его выполнения (там внутри react-ий setState, который может быть обработан асинхронно). Где не критично пустил его на самотёк.


  1. faiwer
    11.01.2017 14:01

    deleted, не туда :(