Почему популярные теги JavaScript не обрабатывают синхронный асинхронный script?

Как ковбой говорит в комментариях здесь, мы все хотим" написать [неблокирующий JavaScript] асинхронный код в стиле, подобном этому:

 try 
 {
    var foo = getSomething();   // async call that would normally block
    var bar = doSomething(foo);  
    console.log(bar); 
 } 
 catch (error) 
 {
    console.error(error);
 }

"

Итак, люди придумали решение этой проблемы, например

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

Итак, почему компиляторы/интерпретаторы javascript не могут просто блокировать заявления, которые мы в настоящее время знаем как "блокирующие"? Итак, почему компиляторы/интерпретаторы javascript не могут обрабатывать синтаксис синхронизации выше AS, если бы мы написали его в стиле асинхронизации? "

Например, при обработке getSomething() выше, компилятор/интерпретатор мог просто сказать: "Этот оператор является вызовом [файловой системы/сетевого ресурса /...], поэтому я сделаю заметку для прослушивания ответов от этого звонка, а тем временем и с тем, что в моем цикле событий". Когда вызов вернется, выполнение может перейти к doSomething().

Вы все равно сохранили бы все основные функции популярных сред выполнения JavaScript

  • однопоточный
  • цикл событий
  • блокирующие операции (I/O, сеть, таймеры ожидания) обрабатываются "асинхронно"

Это будет просто настройка синтаксиса, которая позволит интерпретатору приостанавливать выполнение любого заданного бита кода всякий раз, когда IT DETECTS выполняет асинхронную операцию, и вместо необходимости обратного вызова код просто продолжается после строки после вызова асинхронного вызова когда вызов возвращается.

Как Джереми говорит

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

Почему бы и нет? (Как в "Почему этого не может быть?"... Меня не интересует урок истории)

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

Вы могли бы реализовать его с помощью

  • выражение типа "use noblock"; (немного похожее на "use strict";), чтобы включить этот режим для всей страницы кода. EDIT: "use noblock"; был плохим выбором и ввел в заблуждение некоторых ответчиков, что я пытался вообще изменить характер обычных сценариев JavaScript. Что-то вроде 'use syncsyntax'; может лучше описать его.
  • какой-то оператор parallel(fn, fn, ...);, позволяющий выполнять операции параллельно, в "use syncsyntax"; mode - например, для одновременного запуска нескольких асинхронных действий.
  • EDIT: простой синтаксис стиля синхронизации 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 приостанавливает выполнение этого кода, как обсуждалось).

Ответ 1

Почему бы и нет? Нет причин, это просто не было сделано.

И здесь, в 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 быть асинхронными, но оба они могли быть.)

Пример Live (требуется современный браузер, например, 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.

Ответ 2

Итак, почему компиляторы/интерпретаторы javascript не могут просто блокировать заявления, которые мы в настоящее время знаем как "блокирующие"?

Из-за concurrency control. Мы хотим, чтобы они блокировались, так что (в однопоточной природе 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

Ответ 3

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

Вы не можете сделать "use noblock", потому что на этом этапе не может произойти другая работа. Это означает, что ваш пользовательский интерфейс не будет обновляться. Вы не можете отвечать на мышь или другое событие ввода от пользователя. Вы не можете перерисовать экран. Ничего.

Итак, вы хотите знать, почему? Потому что javascript может привести к изменению отображения. Если бы вы смогли сделать оба одновременно, у вас были бы все эти ужасные условия гонки с вашим кодом и дисплеем. Вы могли бы подумать, что вы что-то переместили на экране, но оно не нарисовано, или оно нарисовано, и вы переместили его после того, как оно нарисовано, и теперь оно должно снова нарисоваться и т.д. Этот асинхронный характер позволяет для любого данного события при выполнении стек, чтобы иметь хорошее состояние - ничего не изменит данные, которые используются во время выполнения.

Это не значит, что то, чего вы хотите, не существует, в какой-то форме.

Библиотека async позволяет вам делать что-то вроде вашей идеи parallel (среди прочих).

Генераторы/async/wait позволят вам писать код, который СМОТРЕТЬ, как вы хотите (хотя он будет асинхронным по своей природе).

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

Ответ 4

В других ответах обсуждались проблемы многопоточности и parallelism. Однако я хочу напрямую ответить на ваш ответ.

Почему бы и нет? (Как в "Почему этого не может быть?"... Меня не интересует урок истории)

Абсолютно без причины. ECMAScript - спецификация JavaScript ничего не говорит о concurrency, он не указывает, что код заказа запущен, он не указывает цикл события или события вообще, и он делает не указывать ничего о блокировке или не блокировании.

Способ concurrency работает в JavaScript, определяется его хост-средой - в браузере, например, DOM и DOM задают семантику цикла события. Функции async, такие как setTimeout, относятся только к DOM, а не к языку JavaScript.

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