Спагетти в последовательном вызове асинхронных функций. Теория и практика

    В продолжение статьи Последовательный вызов асинхронных функций.

    Часть 1. Теория


    Большинство традиционных, не-веб языков программирования являются синхронными (блокирующими).
    Как можно определить, синхронный или же асинхронный данный язык? Например, по наличию/отсутствию функции sleep (может называться также delay, pause и т.д.), когда программа полностью останавливается на определённое количество времени.

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

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

    Преимущества Многопоточности:
    • Не нужно менять мышление, переобучаться из традиционных блокирующих языков (С++, Java, Python)
    • Если у вас многопроцессорный компьютер и программа написана с использованием потоков и они не блокируют друг друга, т.е. оперируют независивыми данными то программа на таком процессоре будет работать несколько быстрее

    Недостатки Многопоточности:
    • Каждому потоку нужно место для работы с данными, поэтому каждый поток отъедает оперативную память даже если эта память не используется и поток спит.
      Раньше это была большая проблема, сейчас память стоит дёшево, но всё равно как-то неприятно становится когда простая но многопоточная Java программа занимает в памяти 2 гб.
    • Если два потока используют один и тот же дефицитный ресурс (объект в памяти, сетевое соединение и т.д), может произойти состязание потоков за ресурс (Race Condition). «Может произойти» это даже хуже, чем если бы мы сказали «произойдёт».
      Для борьбы с состязанием потоков используются флаги, семафоры и т.д., у которых одна цель — заставить другие потоки ждать дефицитный ресурс. Но тогда может возникнуть проблема дедлоков, когда поток A ждёт поток B, а B ждет A.
      Это два самых больших недостатка мультипоточности, это настолько большая проблема, что, например, в собеседовании при приёме на работу Java-программиста обязательно присутствуют вопросы по потокам вроде "чем отличается Thread.start() от Thread.run()?", "как бороться с дедлоками?" и т.д. Java программисты тратят огромное количество времени на героическое создание потоков а потом на не менее героический преодоление проблем связанных с этим.
      Как вы, наверное, знаете, разработка традиционных процессоров упёрлась в определённой предел, и больше не удаётся повысить производительность гонкой гигагерцев. В этом случае мультипоточность даёт большой прирост в обработке большого количества более-менее независимых данных, например при кодировании видео.
      Несмотря на это, впечатление от многозадачности через многопоточность со стороны программиста складывается такое: "невероятно усложнить программу так, чтобы потратилась оперативная память на то чтобы 90% времени потоки ждали друг друга, а как только перестали ждать вступали в борьбу друг с другом угрожая устроить в дедлок".

    В Javascript же чтобы создать паралленую задачу нужно написать всего лишь:
    setTimeout(function () {
    	console.log('Async');
    }, 0);

    или

    button.addEventListener('click', function () {
    	console.log('Async click');
    }, false)


    Однако «параллельная задача» не значит что на десятиядерном процессоре ваш JavaScript заработает быстрее. JavaScript потоконейтрален, в спецификации ECMA не описано как JavaScript машина реализует многозадачность. Насколько я знаю, все существующие реализации JavaScript используют вариант многозадачности типа «Потоки в пространстве пользователя» (процессор быстро-быстро переключает задачи прерывая по таймеру внутри одного процесса) что однако не гарантирует что ядерная многопоточность никогда в будущем не сможет появиться и в JavaScript.
    Забегая вперёд скажу, что в конце концов потоки всё-таки насильно были введены в JavaScript немного странным образом через Web Worker, но об этом будет рассказано ниже, во второй части.

    Итак, в стандартном JavaScript всё сделано иначе, через бесконечный цикл событий (Event Loop) и неблокирующие вызовы. В главном и единственном UI потоке работает этот Event Loop, который обращается к очереди коллбэков и отбирает и последовательно их выполняет пока очередь не очистится.
    Вызов setTimeout, onclick, и XmlHttpRequest с флагом true помещает новый коллбек в очередь событий. Когда коллбек будет выбран из очереди и выполнится, в процессе выполнения он может поставить в очередь ещё один коллбек и т.д.
    Если вы хотите быстро работающий сайт с обильным JavaScript, который не "грузится два часа" — вам следует отсрочить как можно больше операций и как можно быстрее выскочить в главный поток чтобы освободить UI, а менеджер событий сам разберётся когда и что вызывать из очереди, и элементы будут подгружаться постепенно.
    Процесс сканирования очереди колбеков никогда не прерывается но и никогда не ждёт. Хотя конечную скорость работы самой программы постепенная подгрузка данных не изменит, но восприниматься посетителем асинхронный сайт с постепенно появляющимися элементами будет как более быстрый.

    JavaScript очень хорошо приспособлен для асинхронной работы и был задуман именно таким.
    К сожалению в синтаксисе есть синхронные исключения — это команды alert, confirm, promt и xmlhttprequest с флагом false которые всё блокируют.
    Настоятельно не рекомендуется использовать эти команды ни в каких случаях, кроме, пожалуй, одного исключения о котором пойдёт речь в конце нашей статьи.
    Асинхронный вызов всегда лучше синхронного с точки зрения производительности. Посмотрите, например, на nginx — он стал суперпопулярным именно из-за высокой производительности, которая достигается, в основном, асинхронной работе.
    К моему большому сожалению, node.js всё-таки не удержался и ввёл ещё одну синхронную команду — require. Пока эта команда есть в node.js, я, лично никогда не буду использовать его, ибо убеждён что производительность будет всегда хромать.

    Зачем же в асинхронный язык вводятся синхронные команды, которые портят всю идеологию языка?
    Во первых, JavaScript машина работает не сама по себе, а в броузере, в котором сидит пользователь, и блокирующие команды были добавлены не в язык JavaScript, а в среду, которая его окружает — броузер, хотя нам трудно логически разделить эти понятия.
    «С другой стороны броузера» сидят программисты, самые разные, пришедшие из разных языков, чаще всего синхронных. Писать асинхронный код намного сложнее, это требует совершенно другого мышления.
    Поэтому "по многочисленным просьбам программистов слабо понимающих асинхронность", добавили привычные порочные синхронные функции, полностью останавливающие Event Loop.
    Всегда есть возможность выполнить задачу асинхронно, но соблазн упростить себе жизнь, заменив асинхронный вызов на синхронный слишком велик.

    В чём заключается сложность асинхронной разработки?
    К примеру, рано или поздно любой JavaScript программист столкнётся с таким «багом» (Один из самых популярных вопросов на StackOverflow):
    Серверный код

    <?php
    #booklist.php
    header('Content-type: application/json');
    echo json_encode(array(1, 2, 88));
    ?>


    Клиентский код

    var getBookList = function () {
    	var boolListReturn;
    	$.ajax({
    		url : 'bookList.php',
    		dataType : 'json',
    		success : function (data) {
    			boolListReturn = data;
    		}
    	});
    	return boolListReturn;
    };
    console.log(getBookListSorted()); // Почему-то не работает :)


    Здесь, разумеется, непонимание вызвано тем что ajax запрос ушёл в очередь, а console.log остался в главном Ui потоке.
    Когда ajax выполнится успешно, он поместит в очередь коллбек success, который тоже, может быть, когда-нибудь выполнится. Разумеется, console.log уже останется далеко в прошлом с тем что вернула функция (undefined).

    Более правильно немного изменить программу, допустим передав вызов console.log внутрь коллбека success.
    var getBookList = function (callback) {
    	$.ajax({
    		url : 'bookList.php',
    		dataType : 'json',
    		success : function (data) {
    			callback(data);
    		}
    	});
    };
    getBookList(function (bookList) {
    	console.log(bookList);
    });


    Ещё более современный путь — перейти к какому-нибудь удобному интерфейсу работы с коллбеками, например к так называемой концепции «обещание» (promise, также известен как Deferred) — специальному объекту который хранит собственную очередь коллбеков, флаги текущего состояния и другие вкусности.
    var getBookList = function () {
    	return $.ajax({
    		url : 'bookList.php',
    		dataType : 'json',
    	}).promise();
    };
    // В собственную очередь объекта promise коллбек добавляется командой done
    getBookList().done(function (bookList) {
    	console.log(bookList);
    });


    Однако увеличавая нагрузку на коллбеки, может возникнуть вторая проблема, которая заключается в том что использовать больше одной-двух асинхронных команд проблематично.
    Представим что по полученному списку id нам нужно найти названия книг используя другой сервис book.php

    Cерверная часть:
    <?php
    #book.php
    $id = $_REQUEST['id'];
    $response = array(
    	"id" => $id
    );
    switch ($id) {
    	case '1':
    		$response['title'] = "Bobcat 1";
    		break;
    	case '2':
    		$response['title'] = "Lion 2";
    		break;
    	case '88':
    		$response['title'] = "Tiger 88";
    		break;
    }
    header('Content-type: application/json');
    echo json_encode($response);
    ?>


    Наш клиентский код будет таким:

    var getBookList = function () {
    	return $.ajax({
    		url : 'bookList.php',
    		dataType : 'json',
    	}).promise();
    };
    
    var getBook = function (id) {
    	return $.ajax({
    		url : 'book.php?id='+id,
    	}).promise();
    };
    
    getBookList().done(function (bookList) {
    	$.each(bookList, function (index, bookId) {
    		getBook(bookId).done(function (book) {
    			console.log(book.title);
    		});
    	});
    });


    Вот этот трёхэтажный код уже не очень. Разобраться, что тут происходит ещё, конечно, можно, но большой уровень вложенности очень мешает и становится местом где потенциально могут возникнуть баги.
    Вот один из багов:. Если список id был отсортированный, может произойти потеря сортировки. К примеру, если некоторые запросы возвращаются медленнее чем другие, или просто у пользователя параллельно запущен качаться торрент, то скорость выдачи результатов запросов может «скакать».
    На php мы сэмулируем это командой sleep:

    case '2':
    sleep(2);
    $response['title'] = «Lion 2»;
    break;

    наш скрипт выведет
    Bobcat 1
    Tiger 88
    Lion 2
    Здесь видна беда, ведь наш список больше не отсортирован по алфавиту!..
    Как же нам сохранить упорядоченность списка при том что запросы занимают разное время?
    Эта проблема не так проста как кажется, здесь уже даже объекты-обещания (promise) не сильно помогут, попробуйте решить эту проблему сами и вы на своей шкуре почувствуете драматичность ситуации.

    Часть 2. Практика


    Посмотрите на этот неполный список библиотек для JavaScript:
    async.js, async, async-mini, atbar, begin, chainsaw, channels, Cinch, cloudd, deferred, each, EventProxy.js, fiberize, fibers, fibers-promise, asyncblock, first, flow-js, funk, futures, promise, groupie, Ignite, jam, Jscex, JobManager, jsdeferred, LAEH2, miniqueue, $N, nestableflow, node.flow, node-fnqueue, node-chain, node-continuables, node-cron, node-crontab, node-inflow, node_memo, node-parallel, node-promise, narrow, neuron, noflo, observer, poolr, q, read-files, Rubberduck, SCION, seq, sexy, Signals, simple-schedule, Slide, soda.js, Step, stepc, streamline.js, sync, QBox, zo.js, pauseable, waterfall
    Все эти библиотеки-велосипеды обещают решить примерно одну проблему «Write async code in sync form.». Т.е. позволить писать асинхронный код так же легко как и в синхронном стиле.
    Я попробовал большинство из них, но в реальности они не особо помогают. Больших удобств в по сравнению со стандартным jQuery.Deferred я не заметил.
    Но всё же давайте рассмотрим какие есть варианты:

    Вариант 1 «Синхронные вызовы»

    Очевидным решением проблемы вложенных запросов (получить список, пройтись по элементам списка и выполнить ещё по запросу для каждого элемента, при этом сохранив упорядоченность оригинального списка) будет тупо сделать все вызовы синхронными:
    var getBookList = function () {
    	var strReturn;
    	$.ajax({
    		url : '../bookList.php',
    		dataType : 'json',
    		success : function (html) {
    			strReturn = html;
    		},
    		async : false
    	});
    	return strReturn;
    };
    var getBook = function (id) {
    	var strReturn;
    	$.ajax({
    		url : '../book.php?id='+id,
    		success : function (html) {
    			strReturn = html;
    		},
    		async : false
    	});
    	return strReturn;
    };
    var getBookTitles = function () {
    	return $.map(getBookList(), function (val, i) {
    		return getBook(val).title;
    	});
    };
    
    var ul = $('<ul/>').appendTo($('body'));
    $.each(getBookTitles(), function (index, title) {
    	$('<li>'+ title +'</li>').appendTo(ul);
    });

    Это решение из серии «очень быстрое и грязное» ведь запросы не только блокируют броузер но и делают это дольше, ведь каждый следующий запрос ждет предыдущий.

    Достоинства
    1. Простой код, легко ловить ошибки
    2. Легко тестировать

    Недостатки:
    1. Блокирует броузер
    2. Результирующее время равно сумме времени всех запросов


    Вариант 2 «Обещание, которое ждет выполения всех входящих в его список обещаний»

    Список книг сам по себе будет являться одним обещанием(не списком), однако внутри он будет содержать список индивидуальных обещаний и лишь после того как все входящие в него обещания выполнятся,
    будет возвращён результат как массив содержащий синхронные данные

    var getBookTitles = function () {
    	var listOfDeferreds = [];
    	var listDeferred = $.Deferred();
    	getBookList().done(function (bookList) {
    		$.each(bookList, function (i, val) {
    			listOfDeferreds.push(getBook(val));
    		});
    		$.when.apply(null, listOfDeferreds).then(function () {
    			listDeferred.resolve($.map(arguments, function (triple) {
    				return triple[0].title;
    			}));
    		});
    	});
    	return listDeferred.promise();
    };
    getBookTitles().done(function (bookTitles) {
    	$.each(bookTitles, function (index, title) {
    		$('<li>'+ title +'</li>').appendTo('#ul');
    	});
    });	


    Код функции getBookTitles весьма тяжёл. Основная проблема — он ошибкоопасен, сложно ловить проблемы, сложно отлаживать.

    Достоинства этого варианта:
    1. Не блокирует броузер
    2. Результирующее время равно самому долгому из запросов

    Недостатки:
    1. Сложный, ошибкоопасный код
    2. Трудно тестировать


    Вариант 3 «Резервация места для результата в ui»

    В данном случае, получив список id, мы перебираем входящие в него элементы, сразу создаём UI объект.
    В той же итерации запрашиваем вторую порцию асинхронных данных, при этом UI элемент виден через замыкание и мы наполняем его содержимым:
    getBookList().done(function (bookList) {
    		$.each(bookList, function (index, id) {
    			var li = $('<li>Loading...</li>');
    			li.appendTo('#ul');
    			getBook(id).done(function (book) {
    				li.html(book.title);
    		});
    	});
    });


    Достоинства
    1. Не блокирует броузер
    2. Результат показывается сразу как закончится каждый индивидуальный запрос
    3. Запросы идут параллельно

    Недостатки:
    1. Код средней читаемости
    2. Трудно тестировать


    Вариант 4 «Синхронные вызовы в отдельном потоке webworker»

    В процессе написания статьи пришёл в голову немного экзотический вариант — запускать синхронные запросы но в отдельном потоке через WebWorker и модули. При этом броузер не блокируется, но код упрощается.
    Для этого напишем файл для воркера, плюс в нём будет синхронная функция подобная require из node.js.
    // wwarpc.js
    var require = function () {
        // Only load the module if it is not already cached.
        var cache = {};
        var gettext = function (url) {
            var xhr = new XMLHttpRequest();
            xhr.open("GET", url, false);             // sync
            xhr.send(null);
            if (xhr.status && xhr.status != 200) 
            	throw xhr.statusText;
            return xhr.responseText;
        };
        return function (url) {
           if (!cache.hasOwnProperty(url)) {
               try {
                   // Load the text of the module
                   var modtext = gettext(url);
                   // Wrap it in a function
                   var f = new Function("require", "exports", "module", modtext);
                   // Prepare function arguments
                   var context = {};                            // Invoke on empty obj
                   var exports = cache[url] = {};          // API goes here
                   var module = { id: url, uri: url };      // For Modules 1.1
                   f.call(context, require, exports, module);   // Execute the module
               } catch (x) {
                   throw Error("ERROR in require: Can't load module " + url + ": " + x);
               }
           }
           return cache[url];
        }
    }();
    onmessage = function(e){
    	if ( e.data.message !== "start" ) {
    		return
    	}
    	var url = e.data.url;
    	var funcname = e.data.funcname;
    	var args = e.data.args;
    	var module = require(url);
    	postMessage(module[funcname].apply(null, args));
    };


    Вспомогательной функцией для удобного запуска воркера будет такая:
    Она также будет кешировать и воркер и модули для того чтобы не загружать модуль с сервера при каждом вызове.

    
    //Если воркер не поддерживается, эмулируем его через блокирующий эмулятор, например  <script src="http://ie-web-worker.googlecode.com/svn/trunk/worker.js"></script>
    /**
     * Web Worker Asynchroneous Remote Procedure Call
     */
    var wwarpc = function () {
    	var worker;
    	var getWorker = function () { // for lazy load
    		if (worker === undefined) {
    			worker = new Worker("wwarpc.js");
    		}
    		return worker;
    	};
    	return function (url, funcname) {
    		var args = Array.prototype.slice.call(arguments, 2);
    		var d = $.Deferred();
    		var worker = getWorker();
    		worker.onmessage = function (e) {
    			d.resolve(e.data);
    		};
    		worker.postMessage({
    			message : "start",
    			url : url,
    			funcname : funcname,
    			args : args
    		});
    		return d.promise();
    	};
    }();

    Интересно что модули будут node.js-подобные
    //modules/Books.js
    exports.getBookList = function () {
        var the_object = {}; 
        var http_request = new XMLHttpRequest();
        http_request.open( "GET", 'bookList.php', false );
        http_request.send(null);
        if ( http_request.readyState == 4 && http_request.status == 200 ) {
            the_object = JSON.parse( http_request.responseText );
        }
    	return the_object;
    };
    exports.getBook = function (id) {
        var the_object = {}; 
        var http_request = new XMLHttpRequest();
        http_request.open( "GET", 'book.php?id='+id, false );
        http_request.send(null);
        if ( http_request.readyState == 4 && http_request.status == 200 ) {
            the_object = JSON.parse( http_request.responseText );
        }
    	return the_object;
    };
    exports.getBookTitles = function () {
    	var Books = exports;
    	return Array.prototype.map.call(Books.getBookList(), function (val, i) {
    		return Books.getBook(val).title;
    	});
    };

    При этом один и тот же код модулей можно вызывать как синхронно (во время тестирования юнит-тестами) так и асинхронно (во продакшне).
    Благодаря этому основной код будет намного проще, двухэтажный вместо трёх:
    wwarpc('modules/Books.js', 'getBookTitles').done(function (bookTitles) {
    	$.each(bookTitles, function (index, title) {
    		$('<li>'+ title +'</li>').appendTo('#ul');
    	});
    });

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

    Достоинства:
    1. Не блокирует новые броузеры
    2. Простой, легко понимаемый код
    3. Легко тестировать (можно тестировать в синхронном режиме, а вызывать в асинхронном)

    Недостатки:
    1. Блокирует IE и старые броузеры не поддерживающие воркеры
    2. Результирующее время равно сумме времени всех запросов


    Вывод:
    • Вариант 3 «Резервация места для результата в ui» — самый быстрый вариант, но несколько усложнённый код
    • Если читаемость и тестируемость кода важнее чем комфорт пользователей IE, то Вариант 4 «Синхронные вызовы в отдельном потоке webworker» может быть неплохим выбором
    • Следует категорически избегать Варианта 2 («Обещание, которое ждет выполения всех входящих в его список обещаний»)
    • Использование синхронных вызовов ничем нельзя оправдать
    • IE must die!


    Статья написана под впечатлением от следующих материалов:
    Поделиться публикацией

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

      +12
      В нормально спроектированном node.js приложении require вызывается в момент инициализации (который происходит 1 раз) либо, когда очень нужно, вне критических к производительности кусках кода (которые повторяются много раз). Если require тормозит работу Вашего приложения — проблема не в require.
        0
        IE10 поддерживает воркеры.
          0
          Единственная проблема — его ещё нет
          IE6 вышел в 2001 и только что ушёл со сцены до такого состояния что можно его не учитывать.
          С такой скоростью, на IE 10 можно будет положиться в 2020 году
          –4
          AJAX это еще ладно, тут более-менее логично и асинхронные вызовы рулят. А вот когда кодируешь макароны для html5 sql, который работает через асинхронные функции — вот где ненавидишь JS… И думаешь какой мудак такое придумал и ждешь релиза или хотя бы бета версии Dart.
            0
            Вообще, задача какая-то надуманная. В реальном проекте за такое руки можно оторвать программисту. С AJAX проблем обычно нет, обычно получаешь нужные данные за один-два запроса.
              0
              Задача не надуманная, в реальном проекте (что-то вроде соцсети) был только список пользователей и детали каждого пользователя. Rest API фиксированный и менять под свои потребности (чтобы вместо списка возвращал сразу детали пользователя) нельзя.
                0
                В таких ситуациях согласен, приходиться городить костыли. Хотя, думаю, такой API лучше вызывать на сервер-сайде, чтобы закешировать результаты и предоставить более лаконичный API для локального JS кода.
                  0
                  «API лучше вызывать на сервер-сайде» — это то же самое что сделать вызовы синхронными, не так ли? Сервер-сайд же всегда синхронен. Т.е. сервер синхронно обойдёт весь список и сформирует новый список.

                  И кэш совершенно не поможет, если нужны актуальные данные. Вот, к примеру, добавится четвертый элемент в список, как сервер узнает что список изменён и текущий кэш больше невалидный? Либо сервер должен опрашивать каждую секунду оригинальный API из-за чего просядет перформанс, либо сервер оригинальный API будет опрашивать при каждом запросе. Но зачем тогда вообще нужен кэш?
                    0
                    >Сервер-сайд же всегда синхронен
                    нет, не всегда, даже ваш php кажется умеет потоки, правда через одно место, но умеет

                    >Но зачем тогда вообще нужен кэш?
                    Кеш нужен, чтобы ваши клиенты не матюгались, что их страничка грузится over 9000 секунд. Кеш бывает разный, можно настроить сброс через пару минут, а можно и через час. Важно то, что клиент будет делать один запрос, а не over 9000 пусть и не на ваш, а на сервер API, но это для клиента все равно накладно.
              +1
              Есть сильное подозрение, что и Dart будет испольовать асинхронное API для работы с внешними ресурсами или WebWorkers. Причина банальна: асинхронные интерфейсы лучше приспособлены к условиям параллельного выполнения. Так что приучайтесь работать с колбэками и promises.

              Есть ряд идей относительно поддержки асинхронных вызовов на уровне языка: async-await в C# или await-defer в IcedCoffeeScript
                +3
                Не нужно пихать асинхронные вызовы везде, где можно. В dart надеюсь такого не будет или будет выбор.

                Зачем мне асинхронный вызов для SQL запроса — городить из-за этого кучу лишнего кода. Когда, по сути, без получения этих данных нужно их всё равно ждать.

                В java, например, есть выбор. Хочешь асинхронно создавай Future и работай дальше. Не хочешь, просто делай вызов. А в javascript — всё у нас будет асинхронно, ну и получается в итоге полный пздец в коде.
                  +1
                  >> Зачем мне асинхронный вызов для SQL запроса

                  Например, запись статистики
                    0
                    Но в большинстве задач это лишнее и достаточно синхронного вызова функции. Но выбора нет и приходиться писать кучу кода ради одного sql запроса.
                +1
                Я хорошо понимаю, о чем вы. Но JS тут не при чем — это все проблемы асинхронного API. Никакими дартами асинхронное не сделать синхронным.

                А макароны эти вполне решабельны (за подробностями в личку, т.к. пока не production).
                  –2
                  Понятно, что это API и понятно для какого языка проектировалось это API. Просто, надеюсь, что в dart'е это сделают удобнее.
                    +2
                    Вообще-то DOM API считаются языко-независимыми:)
                  0
                  Я лучше макароны буду распутывать, чем писать для веба на не прототипном языке.
                  +7
                  *facepalm*
                  вот вам немножко знаний.

                  >Асинхронный вызов всегда лучше синхронного с точки зрения производительности. Посмотрите, например, на nginx — он стал суперпопулярным именно из-за высокой производительности, которая достигается, в основном, асинхронной работе.
                  Ну не надо путать то когда ты работаешь с системными вызовами и уже более высокоуровневые надстройки. Нет никакого оверхеда на синхронные вызовы. Посмотрите на C#, Haskell, Erlang, Python и то как решаются эти проблемы там.
                    0
                    Большое спасибо за книгу
                    На синхронный вызов, разумеется, нет оверхеда, но на отдельный поток (неважно, ядерный или виртуальный) оверхед есть и по памяти и по cpu. В апаче, например, каждый коннект жрёт 10 мб минимум.
                      0
                      Надеюсь понимаете что те 10мб — это виртуальная память под стэк, в котором хранится текущее состояние. Так вот, давайте теперь пойдём по пути, который вам так нравится и возьмём nginx. В nginx'е в heap'е выделяется кусок памяти под текущее состояние и реализуется fsm'ка, но даже тут мы можем применить магию вроде protothreads и получить безстэковые треды и при этом программист будет работать с синхронным API. Компилятор будет генерировать точно такой же код, который мы бы получили используя кучу switch/case'ов без protothread'ов.
                      Когда говорят про асинхронные вызовы и производительность, подразумевают системные вызовы, а не то что мы можем вокруг них накрутить и сделать всё так что программист будет работать с синхронным API.
                    +1
                    Promise !== Deferred
                      0
                      С этим можно поспорить.
                      Под promise понимается несколько понятий.
                      Вот, например есть концепция CommonJS/Promises A jQuery.Deferred основан на ней.«jQuery.Deferred is based on the CommonJS Promises/A design». С этой точки зрения jQuery Deferred===CommonJS Promise

                      Ну и есть понятие конкретного promise как откушенный кусок от Deferred без функций reject и resolve, здесь — да, Promise !== Deferred
                        0
                        Так как в статье речь идет об использовании именно jQuery.Deferred, то высказывание «promise, также известен как Deferred» как минимум не совсем корректно.

                        Про CommonJS Promises/A я знаю, но, прошу заметить, там описана концепция именно «promise», то есть «отложенного» значения, а не структуры непосредственно реализовывающей возврат значения, как это делает jQuery Deferred. Так что, я продолжаю настаивать, что Deferred !== Promise.
                      +2
                      man корутины
                        0
                        Корутины обречены в JavaScript, у них нет будущего, т.к. программист не может быть уверен в собственном коде.
                        Например
                        var a = my.b;
                        my.b = my.c
                        my.c = get(a);
                        

                        Что произойдёт если get сделает yield и возовновит работу после каких-то там событий? Эти события могут работать с my и они его увидят в промежуточном состоянии когда my.b = my.c потому что my.c ещё не обновлён.

                        В топку! Без изменения синтаксиса языка ничего уже не улучшить.
                        Coroutines no!, generators yes!
                        +1
                        Проблемы с выдачей отсортированного списка несколько надуманны — можно делать запрос не для каждой айдишки, а для массива, возвращая с сервера массив объектов.
                          0
                          я наверное, поспешил с комментарием, да :) nevermind
                          +1
                          Советую посмотреть на node-fibers и мою библиотеку common-node.
                            +1
                            Понятия синхронности и ассинхронности связаны с порядком вычислений. В первом случае он важен, во втором не имеет значения. блокировки это самый простой способ обеспечить последовательное вычисление. Вообще говоря, в классическом джаваскрипте нет примитивов для рассинхронизации порядка вычислений. в популярных реализациях это делается исключительно на уровне рантайма. Поэтому говорить об ассинхронности js как языка в данном случае безосновательно.
                              +1
                              К моему большому сожалению, node.js всё-таки не удержался и ввёл ещё одну синхронную комманду — require. Пока эта комманда есть в node.js, я, лично никогда не буду использовать его, ибо убеждён что производительность будет всегда хромать.

                              Все require вызываются при инициализации приложения, какое влияние на производительность?
                                –2
                                http.createServer(function(request, response) {
                                	var mymod = require("./mymod.js");
                                }).listen(8888);
                                — не при инициализации приложения.
                                Никто не спорит что она закешированная и всё такое, но она синхронная
                                  +2
                                  Жесть. Кто вам такое показал?

                                  Вот как оно делается:

                                  var mymod = require("./mymod.js");
                                  http.createServer(function(request, response) {

                                  }).listen(8888);

                                  Библиотеки подключаются до инициализации сервера, как и все остальное, что можно сделать при запуске приложения.
                                    +1
                                    А смысл это делать каждый запрос?
                                    var mymod = require("./mymod.js");
                                    http.createServer(function(request, response) {
                                        // use mymod here
                                    }).listen(8888);
                                    
                                      0
                                      Вот еще очень удобный вариант.
                                      http.createServer(function(request, response) {
                                      var counter = 0;
                                      for(var i1 = 0; i1 < 1000000000; i1++)
                                      for(var i2 = 0; i2 < 1000000000; i2++)
                                      for(var i3 = 0; i3 < 1000000000; i3++)
                                      counter++;
                                      var mymod = require("./mymod.js");
                                      }).listen(8888);
                                    0
                                    Интересная статья, как раз с похожей задачей сейчас имею дело, но, на другом языке. По-моему вариант с $.when можно немного упростить, но, в целом, суть и так ясна. Спасибо!
                                      0
                                      Почему «Вариант 2» Нужно избегать? На twisted'е я так и пишу, тут разницы особой нет JS это или Python. Чем плох этот вариант? Сложностью? Это — проблемы того кто не понимает сам что пишет.
                                        0
                                        Сложность — раз
                                        резолв ждёт выполнения самого медленного запроса из списка — два
                                        неопределённость — если хотя бы один из элементов вернул reject, весь массив реджектится или всё-таки резолвятся валидные значения? — три
                                        +1
                                        ну вот опять этот некрасивый каскадный код. что мешает вам писать иначе? это же Javascript!

                                        (function() {
                                        someAction1(data, callback1);

                                        function callback1(error, data) {
                                        someAction2(data, callback2);
                                        }

                                        function callback2(error, data) {
                                        someAction3(data, callback3);
                                        }

                                        /* и т.д. */
                                        })();


                                        конечно немного длиннее, зато прозрачно и последовательно.

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

                                        А вот предложение про node.js и require как уже заметили вообще не имеет смысла.
                                          +1
                                          В Haskell, например, благодаря монадам и синхронный, и асинхронный код выглядят почти идентично
                                          В C# (где тоже есть монады (LINQ)) асинхронный код тоже можно переписать на них.

                                          Быстрым поиском нашёл статью Understanding Monads With JavaScript
                                          • НЛО прилетело и опубликовало эту надпись здесь
                                            • НЛО прилетело и опубликовало эту надпись здесь
                                              • НЛО прилетело и опубликовало эту надпись здесь
                                              • НЛО прилетело и опубликовало эту надпись здесь
                                                +2
                                                Меня удивил вывод про IE must die. Я так понимаю что его как минимум надо заменить на «Старые браузеры не поддерживающие webworker — must die».

                                                А если вспомнить что на 1 февраля 2012 WebWorker это все еще draft (как собственно и весь html5) то фразу следует переделать в «Старые браузеры не поддерживающие неокончательно принятую фичю в которой еще может все измениться — must die».

                                                Как-то неправильно это звучит в итоге…
                                                  –2
                                                  JavaScript 1.7, где есть генераторы, которые могут решать эту же проблему, не драфт с октября 2006, а в IE не поддерживаются тоже
                                                    +3
                                                    интересный ответ, но:

                                                    в последней версии ecmascript 5.1 (июнь 2011) генераторов нет, yield только как future reserved word. А современные браузеры ориентируются именно на ecmascript.

                                                    генераторы уже давно есть в javaScript 1.7 который курируется Mozilla Foundation, и собственно только используется в js движке от Mozilla (причем не как дефолт, а как расширение которое можно включить типом скрипта).

                                                    Делает ли это отстоем все остальные (не Mozilla based) браузеры? Я думаю нет.

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

                                                Самое читаемое