Почему популярные JavaScript-приложения не могут обрабатывать синхронно выглядящий асинхронный скрипт?
Как говорит ковбой в комментариях здесь, мы все хотим "написать [неблокирующий JavaScript] асинхронный код в стиле, подобном следующему:
try
{
var foo = getSomething(); // async call that would normally block
var bar = doSomething(foo);
console.log(bar);
}
catch (error)
{
console.error(error);
}
"
Таким образом, люди придумали решения этой проблемы, как
- библиотеки обратного вызова (например, async)
- обещания
- шаблоны событий
- рационализировать
- домены и
- генераторы.
Но ни один из них не приводит к такому простому и понятному коду, как приведенный выше код в стиле синхронизации.
Так почему же компиляторы / интерпретаторы javascript не могут просто НЕ блокировать операторы, которые мы в настоящее время знаем как "блокирование"? Так почему же компиляторы / интерпретаторы javascript не могут обрабатывать синтаксис синхронизации выше AS, если бы мы написали его в асинхронном стиле?"
Например, при обработке getSomething()
выше, компилятор / интерпретатор мог просто сказать: "это утверждение является вызовом [файловой системы / сетевого ресурса /...], поэтому я сделаю заметку, чтобы прослушать ответы на этот вызов и в то же время разобраться с чем угодно. в моем цикле событий ". Когда вызов возвращается, выполнение может перейти к doSomething()
,
Вы по-прежнему сохраняете все основные функции популярных сред выполнения JavaScript
- однопоточный
- цикл событий
- операции блокировки (ввод / вывод, сеть, таймеры ожидания) обрабатываются "асинхронно"
Это будет просто настройка синтаксиса, которая позволит интерпретатору приостанавливать выполнение любого заданного бита кода всякий раз, когда IT ОБНАРУЖИВАЕТ асинхронную операцию, и вместо необходимости обратных вызовов код просто продолжается со строки после асинхронного вызова при вызове. возвращается.
Как говорит Джереми
во время выполнения JavaScript нет ничего, что могло бы превентивно приостановить выполнение заданной задачи, позволить некоторому другому коду выполнить некоторое время, а затем возобновить исходную задачу
Почему бы и нет? (Например, "почему этого не может быть?"... меня не интересует урок истории)
Почему разработчик должен заботиться о том, является ли утверждение блокирующим или нет? Компьютеры предназначены для автоматизации вещей, в которых люди плохо разбираются (например, пишут неблокирующий код).
Возможно, вы могли бы реализовать это с
- заявление как
"use noblock"
;(немного как"use strict";
) включить этот "режим" для всей страницы кода. РЕДАКТИРОВАТЬ:"use noblock"
; Это был плохой выбор, и он ввел некоторых в заблуждение тем, что я пытался полностью изменить природу общих сред выполнения JavaScript. Что-то вроде'use syncsyntax';
может лучше описать это. - наподобие
parallel(fn, fn, ...);
заявление, позволяющее вам запускать вещи параллельно, в то время как в"use syncsyntax"
; режим - например, для одновременного запуска нескольких асинхронных операций - РЕДАКТИРОВАТЬ: простой синтаксис в стиле синхронизации
wait()
, который будет использоваться вместоsetTimeout()
в"use syncsyntax"
; Режим
РЕДАКТИРОВАТЬ:
В качестве примера вместо записи (стандартная версия обратного вызова)
function fnInsertDB(myString, fnNextTask) {
fnDAL('insert into tbl (field) values (' + myString + ');', function(recordID) {
fnNextTask(recordID);
});
}
fnInsertDB('stuff', fnDeleteDB);
Вы могли бы написать
'use syncsyntax';
function fnInsertDB(myString) {
return fnDAL('insert into tbl (field) values (' + myString ');'); // returns recordID
}
var recordID = fnInsertDB('stuff');
fnDeleteDB(recordID);
syncsyntax
Версия будет обрабатываться точно так же, как и стандартная версия, но гораздо проще понять, что задумал программист (если вы понимаете, что syncsyntax
приостанавливает выполнение этого кода, как обсуждалось).
4 ответа
Почему бы и нет? Нет причин, просто это не было сделано.
И вот в 2017 году это было сделано в ES2017: async
функции могут использовать await
ждать, не блокируя, результат обещания. Вы можете написать свой код, как это, если getSomething
возвращает обещание (обратите внимание на await
) и если это внутри async
функция:
try
{
var foo = await getSomething();
var bar = doSomething(foo);
console.log(bar);
}
catch (error)
{
console.error(error);
}
(Я предполагал, что вы только намеревались getSomething
быть асинхронным, но они оба могут быть.)
Живой пример (требуется современный браузер, такой как Chrome):
function getSomething() {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() < 0.5) {
reject(new Error("failed"));
} else {
resolve(Math.floor(Math.random() * 100));
}
}, 200);
});
}
function doSomething(x) {
return x * 2;
}
(async () => {
try
{
var foo = await getSomething();
console.log("foo:", foo);
var bar = doSomething(foo);
console.log("bar:", bar);
}
catch (error)
{
console.error(error);
}
})();
The first promise fails half the time, so click Run repeatedly to see both failure and success.
Вы отметили свой вопрос с NodeJS. Если вы оберните Node API в обещания (например, с помощью promisify), вы можете написать хороший прямой синхронный код, который выполняется асинхронно.
Так почему же компиляторы / интерпретаторы javascript не могут просто НЕ блокировать операторы, которые мы в настоящее время знаем как "блокирование"?
Из-за контроля параллелизма. Мы хотим, чтобы они блокировались, чтобы (в однопоточной природе JavaScript) мы были в безопасности от условий гонки, которые изменяют состояние нашей функции, пока мы ее выполняем. У нас не должно быть интерпретатора, который приостанавливает выполнение текущей функции при любом произвольном выражении / выражении и возобновляет работу с какой-то другой частью программы.
Пример:
function Bank() {
this.savings = 0;
}
Bank.prototype.transfer = function(howMuch) {
var savings = this.savings;
this.savings = savings + +howMuch(); // we expect `howMuch()` to be blocking
}
Синхронный код:
var bank = new Bank();
setTimeout(function() {
bank.transfer(prompt); // Enter 5
alert(bank.savings); // 5
}, 0);
setTimeout(function() {
bank.transfer(prompt); // Enter 3
alert(bank.savings); // 8
}, 100);
Асинхронный, произвольно неблокирующий код:
function guiPrompt() {
"use noblock";
// open form
// wait for user input
// close form
return input;
}
var bank = new Bank();
setTimeout(function() {
bank.transfer(guiPrompt); // Enter 5
alert(bank.savings); // 5
}, 0);
setTimeout(function() {
bank.transfer(guiPrompt); // Enter 3
alert(bank.savings); // 3 // WTF?!
}, 100);
во время выполнения JavaScript нет ничего, что могло бы превентивно приостановить выполнение заданной задачи, позволить некоторому другому коду выполнить некоторое время, а затем возобновить исходную задачу
Почему бы и нет?
Для простоты и безопасности см. Выше. (И для урока истории: вот как это было сделано)
Однако это уже не так. В генераторах ES6 есть нечто, что позволяет вам явно приостановить выполнение текущего генератора функций: yield
ключевое слово.
Поскольку язык развивается, есть также async
а также await
ключевые слова запланированы для ES7.
генераторы [… не…] приводят к такому простому и понятному коду, как приведенный выше код синхронизации.
Но они делают! Это даже прямо в этой статье:
suspend(function* () {
// ^ "use noblock" - this "function" doesn't run continuously
try {
var foo = yield getSomething();
// ^^^^^ async call that does not block the thread
var bar = doSomething(foo);
console.log(bar);
} catch (error) {
console.error(error);
}
})
Здесь также есть очень хорошая статья на эту тему: http://howtonode.org/generators-vs-fibers
Другие ответы говорили о проблемах многопоточности и параллелизма. Однако я хочу обратиться к вашему ответу напрямую.
Почему бы и нет? (Например, "почему этого не может быть?"... меня не интересует урок истории)
Абсолютно без причины. ECMAScript - спецификация JavaScript ничего не говорит о параллелизме, в нем не указан код заказа, в котором он выполняется, он не определяет цикл событий или события вообще, и он не указывает ничего о блокировании или не блокировании.
Способ работы параллелизма в JavaScript определяется его хост-средой - например, в браузере это DOM, а DOM определяет семантику цикла обработки событий. "асинхронные" функции, такие как setTimeout
только забота DOM, а не язык JavaScript.
Более того, нет ничего, что говорило бы, что среды выполнения JavaScript должны быть однопоточными и так далее. Если у вас есть последовательный код, указывается порядок выполнения, но ничто не мешает встраивать язык JavaScript в многопоточную среду.
Потому что интерпретаторы Javascript являются однопоточными, управляемыми событиями. Вот как был разработан исходный язык.
Ты не можешь сделать "use noblock"
потому что никакая другая работа не может произойти во время этой фазы. Это означает, что ваш пользовательский интерфейс не будет обновляться. Вы не можете ответить на мышь или другое событие ввода от пользователя. Вы не можете перерисовать экран. Ничего такого.
Так ты хочешь знать почему? Потому что JavaScript может привести к изменению отображения. Если бы вы смогли сделать оба одновременно, у вас были бы все эти ужасные условия гонки с вашим кодом и дисплеем. Вы можете подумать, что вы что-то переместили на экран, но оно не нарисовано, или оно нарисовало, и вы переместили это после того, как оно нарисовало, и теперь оно должно быть нарисовано снова и т. Д. Эта асинхронная природа допускает любое событие в выполнении стек, чтобы иметь известное хорошее состояние - ничто не изменит данные, которые используются, пока они выполняются.
Это не значит, что то, что вы хотите, не существует в какой-то форме.
async
библиотека позволяет делать такие вещи, как ваш parallel
идея (среди прочих).
Generators / async / wait позволит вам написать код, который выглядит как то, что вы хотите (хотя по своей природе он будет асинхронным).
Хотя вы делаете ложное утверждение здесь - люди НЕ плохи в написании асинхронного кода.