» » » Упрощение асинхронного кода на JavaScript с внедрением асинхронных функций из ES2016

 

Упрощение асинхронного кода на JavaScript с внедрением асинхронных функций из ES2016

Автор: admin от 30-10-2015, 17:45, посмотрело: 759

Хотя мы еще продолжаем работу над внедрением поддержки ES6/2015, команда Chackra также смотри за пределы ES2016 и, в частности, на асинхронные функции. Мы рады объявить об экспериментальной поддержке async-функций в Microsoft Edge, начиная со сборки Microsoft Edge (EdgeHTML 13.10547).



Асинхронные функции в ES7/ES2016


Ключевые слова async и await, как часть предложения по внедрению асинхронных функций, нацелены на упрощение написания асинхронного кода. Это одна из ключевых возможностей современного C# и часто запрашиваемая опция со стороны javascript-разработчиков. До введения асинхронных функций и промисов (promise) JS-разработчику приходилось оборачивать весь асинхронный код в отдельные от синхронного кода функции и использовать функции обратного вызова (callback) для работы с результатом асинхронного вычисления. Такой код довольно быстро становится трудно читать и поддерживать.

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

Асинхронные функции базируются на промисах и позволяют сделать следующий шаг. Когда вы добавляете ключевое слово async к функции или стрелочной функции, она автоматически будет возвращать промис. К примеру, следующий код – это типовая программа на ES2015, делающая http-запрос с помощью промисов:

// ES6 code, without async/await 
function httpGet(url) {
    return new Promise(function (resolve, reject) {
        // do the usual Http request
        var request = new XMLHttpRequest();
        request.open('GET', url);

        request.onload = function () {
            if (request.status == 200) {
                resolve(request.response);
            } else {
                reject(Error(request.statusText));
            }
        };

        request.onerror = function () {
            reject(Error('Network Error'));
        };

        request.send();
    });
}

function httpGetJson(url) {
    return new Promise(function (resolve, reject) {
        // check if the URL looks like a JSON file and call httpGet.
        var regex = /.(json)$/i;

        if (regex.test(url)) {
            // call the promise, wait for the result
            resolve(httpGet(url).then(function (response) {
                return response;
            }, function (error) {
                reject(error);
            }));
        } else {
            reject(Error('Bad File Format'));
        }
    });
}

httpGetJson('file.json').then(function (response) {
    console.log(response);
}).catch(function (error) {
    console.log(error);
});


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

// ES7 code, with async/await
function httpGet(url) {
    return new Promise(function (resolve, reject) {
        // do the usual Http request
        let request = new XMLHttpRequest();
        request.open('GET', url);

        request.onload = function () {
            if (request.status == 200) {
                resolve(request.response);
            } else {
                reject(Error(request.statusText));
            }
        };

        request.onerror = function () {
            reject(Error('Network Error'));
        };

        request.send();
    });
}

async function httpGetJson(url) {
    // check if the URL looks like a JSON file and call httpGet.
    let regex = /.(json)$/i;

    if (regex.test(url)) {
        // call the async function, wait for the result
        return await httpGet(url);
    } else {
        throw Error('Bad Url Format');
    }
}

httpGetJson('file.json').then(function (response) {
    console.log(response);
}).catch(function (error) {
    console.log(error);
});


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

// call the async function, wait for the result
let func = async () => await httpGet(url);
return await func();


Итого:

  • Используйте ключевое слово async при определении любой функции или стрелочной функции, чтобы получить асинхронный код с промисом. Это включает также функции в классах и статичные функции. В последнем случае ключевое слово async нужно указывать после слова static и, соответственно, перед именем функции.

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

  • Если вы не используете ключевое слово await, вы получите сам промис.

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



Как это реализовано в Chakra?


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

Упрощение асинхронного кода на JavaScript с внедрением асинхронных функций из ES2016

Глобальное поведение


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

function spawn(genF, self) {
    return new Promise(function (resolve, reject) {
        var gen = genF.call(self);
        function step(nextF) {
            var next;
            try {
                next = nextF();
            } catch (e) {
                // finished with failure, reject the promise
                reject(e);
                return;
            }
            if (next.done) {
                // finished with success, resolve the promise
                resolve(next.value);
                return;
            }
            // not finished, chain off the yielded promise and `step` again
            Promise.resolve(next.value).then(function (v) {
                step(function () { return gen.next(v); });
            }, function (e) {
                step(function () { return gen.throw(e); });
            });
        }
        step(function () { return gen.next(undefined); });
    });
}


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

Далее движок должен вызвать порождающую функцию из JS-скрипта, чтобы получить промис и выполнить содержимое функции. Чтобы это сделать, когда парсер находит ключевое слово async, движок изменяет AST (абстрактное синтаксическое дерево), представляющее алгоритм, чтобы добавить вызов spawn-функции с телом целевой функции. Как следствие, функция httpGetJson из примера выше конвертируется парсером примерно следующим образом:

function httpGetJson(url) {
    return spawn(function* () {
        // check if the URL looks like a JSON file and call httpGet.
        var regex = /.(json)$/i;

        if (regex.test(url)) {
            // call the async function, wait for the result
            return yield httpGet(url);
        } else {
            throw Error('Bad Url Format');
        }
    }, this);
}


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

Поведение с аргументом по умолчанию


Одна из новых возможностей ES6 – это установка значения по умолчанию для аргумента функции. Когда используется значение по умолчанию, генератор байт-кода установит это значение в начале тела функции.

// original javascript code
function foo(argument = true) {
    // some stuff
}

// representation of the Bytecode Generator's output in javascript
function foo(argument) {
    argument = true;
    // some stuff
}


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

Чтобы реализовать это в Chakra, у команды был выбор из двух вариантов: изменить AST, или реализовать такое поведение напрямую в генераторе байт-кода. Мы выбрали второе и передвинули инициализацию аргументов в начало тела функции напрямую в байт-коде, так как это более простое и понятное решение в рамках нашего движка. Так как для перехвата ошибок из значения по умолчанию нужно было добавить блок try/catch, то нам было проще напрямую изменить байт-код при обнаружении ключевого слова async.

Наконец, сгенерированный байт-код будет напоминать результат, создаваемый для такого кода на javascript:

// representation of the Bytecode Generator's output in javascript
function foo(argument) {
    try {
        argument = true;
    } catch (error) {
        return Promise.reject(error);
    }
    return spawn(function* () { // keep this call as we are in an async function
        // some stuff
    }, this);
}


Как включить поддержку асинхронных функций Microsoft Edge?


Чтобы включить экспериментальную поддержку асинхронных функций в Microsoft Edge, перейдите на страницу about:flags в Microsoft Edge и выберите опцию “Enable experimental javascript features”, как показано ниже:

Упрощение асинхронного кода на JavaScript с внедрением асинхронных функций из ES2016

Асинхронные функции доступны в превью-режиме в рамках программы Windows Insider, начиная со сборки Microsoft Edge 13.10547. Будем рады услышать ваши отзывы по использованию данной функциональности в вашем коде в нашем Twitter @MSEdgeDev или через Connect.

Etienne Baudoux, Software Development Engineer Intern, Chakra Team
Brian Terlson, Senior Program Manager, Chakra Team

Источник: Хабрахабр

Категория: Компании » Microsoft

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

Добавление комментария

Имя:*
E-Mail:
Комментарий:
Полужирный Наклонный текст Подчеркнутый текст Зачеркнутый текст | Выравнивание по левому краю По центру Выравнивание по правому краю | Вставка смайликов Выбор цвета | Скрытый текст Вставка цитаты Преобразовать выбранный текст из транслитерации в кириллицу Вставка спойлера
Введите два слова, показанных на изображении: *