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

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

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

// Click event
$('nav a').click(function(){
    console.log('A click just occurred');
});
// Other code
console.log('I'm some other code');

Функция клика, предоставленная jQuery — яркий пример асинхронности в действии. Разработчик предоставляет код, который будет выполняться в случае возникновения события щелчка. Затем прослушиватель кликов запускается в своем собственном потоке, а оставшийся код выполняется независимо. Как видите, нет необходимости ждать, пока произойдет событие клика, потому что функция клика выполняет наш код, когда это необходимо.

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

Это не идеально

Хотя использование асинхронных принципов в JavaScript явно позволяет упростить разработку многопоточных программ, у него есть свои недостатки. Хотя JavaScript позволяет нам простым способом использовать несколько потоков, сами потоки являются виртуальными; то есть, хотя логика подразумевает использование нескольких потоков, на практике используется только один поток ЦП.

Еще одна проблема, печально известная тем, что разочаровывает разработчиков, — это callback hell. Неизбежно некоторые процессы должны выполняться последовательно; рассмотрим приведенный ниже вариант использования, который извлекает данные с сервера базы данных, отображает их на странице и распространяет страницу.

sql.execute(query, function(err, data){
    // First callback
    if(err){
        // Do something
    }
    render.page(‘home’, data, function(err, data){
        // Second callback
        if(err){
            // Do something
        }
        server.send(data, function(err){
            // Third callback
            if(err){
                // Do something
            }
        });
    });
});

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

Использование промисов

Хотя можно принять меры предосторожности, чтобы избежать ада обратных вызовов, во многих случаях это неизбежно. В связи с этим использование promises становится популярной альтернативой обратным вызовам.

Промисы — это объекты, которые при создании экземпляра могут быть либо выполнены, либо отвергнуты. Выполнение означает, что программа запущена и ошибок не обнаружено; отказ подразумевает обратное. Для каждого выполнения обещания вызывается функция then указанного обещания, а функция catch вызывается для каждого отклонения.

isOne(x)
.then(function(){
    console.log('x is one');
})
.catch(function(){
    return isTwo(x)
})
.then(function(){
    console.log('x is two');
})
.catch(function(){
    console.log('x is neither one Nor two');
})

Рассмотрим функцию isOne. Он возвращает обещание, которое выполняется, если первый предоставленный аргумент равен 1, и отклоняется в противном случае. Предполагая, что обещание выполнено, вызывается функция then, и функция, предоставленная разработчиком в качестве аргумента, выполняется. В качестве альтернативы, если происходит отклонение, создается исключение и вызывается метод catch — снова вызывая любую функцию, предоставленную в качестве аргумента.

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

Bluebird.js

Как указывалось ранее, большинство библиотек JavaScript используют структуру, основанную на обратных вызовах, и на момент написания нативный JavaScript не предоставляет инструментов для использования существующих библиотек способом, управляемым промисами. К счастью, люди, работающие над Bluebird.js, поддержали нас и создали API, упрощающий переход на промисы.

const Promise = require('bluebird');
const myApi = require('myapi');
const myApiProm = Promise.promisifyAll(myApi);

Примечание.Приведенный выше пример демонстрирует использование Bluebird.js внутри программы Node.js. Для браузерной версии потребуется инструмент связывания зависимостей, такой как Browserify.

Вывод

Обещания кажутся решением долгосрочного разочарования, разделяемого разработчиками JavaScript. Несмотря на встроенную поддержку в ES6, отсутствие расширенных функций, таких как обещание, гарантирует, что такие фреймворки, как Bluebird.js, останутся необходимым компонентом.

Я очень ценю любые комментарии или предложения, так что говорите! Чтобы узнать обо всех моих блогах, размышлениях и обсуждениях разработчиков, свяжитесь со мной в Твиттере @andyrichrich