Введение
Многие функции в клиенте JavaScript Diffusion возвращают объект Result
. Этот объект можно использовать для получения возвращаемых значений асинхронно выполняемого кода. Result
реализует then()
метод, который принимает одну или две функции обратного вызова. Первая функция обратного вызова будет вызываться с возвращаемым значением после завершения асинхронного вызова. Вторая функция обратного вызова будет вызвана в случае ошибки.
Важно отметить, что Result
реализует полную спецификацию ES6 Promise и во всех отношениях эквивалентен Promise
. Эта статья предназначена для того, чтобы пролить свет на передовой опыт использования Result
s, возвращаемых из различных функций Diffusion.
Ловушка обратного вызова
При рассмотрении обработчика then()
с точки зрения традиционных функций обратного вызова возникает соблазн рекурсивно обернуть обратные вызовы друг в друга. Это может привести к нечитаемому и потенциально ошибочному коду. Рассмотрим следующий пример. Функция должна последовательно запускать четыре задачи.
- подключиться к серверу Diffusion и получить сеанс
- создать новую тему
- отправить сообщение получателю и получить ответ
- установить ранее созданную тему со значением, полученным в ответе на сообщение
Используя традиционный стиль обратного вызова, это можно записать следующим образом.
function callback_pyramid() { diffusion.connect({ principal : '', credentials : '' }).then( function(session) { session.topics.add('path/to/a/topic', diffusion.topics.TopicType.STRING).then( function() { session.messages.sendRequest('/path/to/message/topic', 'foo').then( function(value) { session.topics.set('path/to/a/topic', diffusion.datatypes.string(), value).then( function(value) { console.log("topic has been updated to "+value); }, // Error callback for session.topics.set function (error) { console.log(error); } ) }, // Error callback for session.messages.sendRequest function (error) { console.log(error); } ); }, // Error callback for session.topics.add function (error) { console.log(error); } ); }, // Error callback for diffusion.connect function (error) { console.log(error); } ); }
Этот код имеет ряд проблем. Каждый дополнительный шаг в последовательности будет создавать новый уровень вложенных обратных вызовов. Это делает код чрезвычайно громоздким и трудным для чтения. В дополнение к этому на каждом уровне должен быть предусмотрен обратный вызов ошибки. Порядок обработчиков ошибок меняется на обратный по отношению к порядку выполняемых действий, что очень затрудняет визуальную связь обработчика ошибок с вызовом, вызвавшим ошибку.
Использование обещаний
Приведенный выше пример можно значительно улучшить. В спецификации Promise A + указано, что then
возвращает другой Promise
. Если функция, переданная обработчику then()
, возвращает значение, Promise
будет разрешено с этим значением. Если, с другой стороны, это значение, возвращаемое обработчиком, является Promise
, тогда Promise
, возвращаемое then()
, будет разрешено, когда Promise
, возвращенное обработчиком, разрешится. Хотя это звучит сложно, это означает, что можно объединить then()
функций в цепочку. Каждый член в цепочке будет выполнен только после того, как завершатся предыдущие участники.
Тот же пример, что и выше, теперь можно записать следующим образом.
function chaining_promises() { diffusion.connect({ principal : '', credentials : '' }).then( function(session) { return session.topics.add('path/to/a/topic', diffusion.topics.TopicType.STRING); } ).then( function() { return session.messages.sendRequest('/path/to/message/topic', 'foo'); } ).then( function(value) { return session.topics.set('path/to/a/topic', diffusion.datatypes.string(), value); } ).then( function(setValue) { console.log("topic has been updated to "+setValue); } ).catch ( // Combined error callback function (error) { console.log(error); } ); }
Обратите внимание, как каждый обработчик, переданный в функцию then()
, возвращает объект Result
. Только когда этот Result
разрешается со значением, будет вызван следующий then()
обработчик в цепочке. Преимущество такого подхода очевидно. Последовательность действий дает плоскую видимую структуру. Дополнительным преимуществом является то, что ошибку в любом элементе этой цепочки можно отловить с помощью одного обработчика ошибок. Promise
, возвращаемый then()
, имеет метод catch()
. Обработчик, переданный этому методу, будет вызван, если какое-либо из действий на предыдущих шагах завершится неудачно.
Использование async / await
Относительно современной особенностью JavaScript, представленной в ES6, является введение ключевых слов async
и await
. Функцию можно объявить асинхронной, поставив перед ней префикс async
. Это указывает на то, что функция неявно вернет Promise
. Внутри асинхронной функции вызовы функций, возвращающих Promise
s, могут иметь префикс await
. Когда функция вызывается, await
заставляет выполнение приостанавливаться, пока обещание не разрешится. Это позволяет еще больше упростить приведенный выше пример.
async function using_async_await() { try { const session = await diffusion.connect({ principal : '', credentials : '' }); await session.topics.add('path/to/a/topic', diffusion.topics.TopicType.STRING); const value = await session.messages.sendRequest('/path/to/message/topic', 'foo'); const setValue = await session.topics.set('path/to/a/topic', diffusion.datatypes.string(), value); console.log("topic has been updated to "+setValue); } // Combined error callback catch(error) { console.log(error); } }
Обработка ошибок теперь происходит с использованием того, что выглядит как традиционный блок try-catch. Объем стандартного кода сокращается, и операторы представляются в виде, напоминающем синхронное последовательное выполнение. Это приводит к еще более управляемому коду.
Резюме
Использование обещаний в JavaScript может значительно снизить сложность асинхронного кода. За счет замены рекурсивной упаковки обратных вызовов на другую более последовательным подходом улучшается удобочитаемость и ремонтопригодность. Введение async
и await
еще больше улучшает это. Объект Result
, возвращаемый многими функциями в Diffusion, реализует спецификацию Promise A + и естественно вписывается в эту парадигму.
Первоначально опубликовано: https://bit.ly/3fmRkS1