Краткое и интересное введение в обещания JavaScript и их использование в Web Bluetooth API.

Если вы еще не видели, я недавно сделал несколько забавных вещей с помощью Web Bluetooth. Когда я разобрался с этой технологией, я узнал, что метод запрос устройства, который позволяет вашему браузеру подключаться к удаленному устройству, возвращает обещание. Но что такое обещание? В спецификации сказано:

«Объект Promise представляет собой окончательное завершение (или сбой) асинхронной операции и ее результирующее значение».

Хорошо, но подождите, что такое асинхронная операция ?! Ууууууррррргх!

Синхронный и асинхронный

Представьте, что вы находитесь в новом модном магазине по продаже пончиков. Как и все в округе. Вам нужно встать в очередь, чтобы купить NutellaFilledRainbowSprinklesUnicornSupreme, и вы не сможете получить его, пока все в очереди перед вами не купят свои пончики.

Так работает синхронный код. Строки кода представляют собой очередь, и они не будут выполняться, пока не завершится предыдущая.

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

Это немного похоже на то, как JavaScript может работать асинхронно. Когда вы вызываете функцию в JavaScript, это все равно, что отдавать ваш заказ официанту. Вызванная вами функция не выполняется немедленно. Как бы вам ни хотелось, ваш пончик не просто внезапно появится на вашем столе. Вызов создает сообщение, которое добавляется в очередь сообщений. Это ваш заказ. В нем может быть много составных частей, напиток, несколько пончиков и т. Д. Эти составные части сообщения являются аргументами функции и переменными, которые необходимо выполнить. «Цикл событий» перемещает сообщения в «стек вызовов». Это ваш официант несет ваш заказ на кухню. Как только стек вызовов опустеет от предыдущих сообщений, ваше сообщение может быть обработано. Официант примет предыдущий заказ с кухни, и у шеф-повара будет место, чтобы начать выстраивать ваши блюда и готовить их. Стек вызовов будет ждать, пока все соответствующие кадры вашего сообщения не будут выполнены, прежде чем само сообщение будет исключено из очереди и ваши пончики будут доставлены на ваш стол.

Обещания

Хорошо, это асинхронное поведение в двух словах. Так что же обещание?

Когда официанты принимают ваш заказ, они по сути обещают принести вам пончик. Вы надеетесь, что они принесут вам пончик, но между тем, как вы заказываете этот пончик, и временем, когда ваш заказ «разрешится», может произойти много разных вещей. Когда ваш заказ сделан, обещание официанта находится в состоянии ожидания, оно не было ни выполнено, ни отклонено. Когда вы можете съесть свой первый вкусный пончик, обещание официанта выполнено. Но что, если ваш официант уронит его до того, как принесет вам? Они нарушили бы свое обещание о пончике, и в этом случае обещание было бы «отвергнуто». После того, как обещание было выполнено или отклонено, оно становится «урегулированным».

Если бы мы попытались написать это на Javascript, это могло бы выглядеть так:

const getDonut = new Promise(
(resolve, reject) => {
    // Promise is pending, we can do other things 
    // while we wait for our donut to arrive.
    if(/* unicorn sprinkles donut is in stock */) {
       resolve("Here's your magical donut!"); // promise is resolved
    } else {
       reject(
         Error("Oh no, we're all outta that flavour")
       ); // promise is rejected
    }
  }
);
getDonut.then((result) => {
  // if we've got our donut console log the result
  console.log(result); 
}, (err) => {
  // if we didn't get our donut, console log what went wrong
  console.log(err);
});

Конструктор обещания принимает один аргумент - функцию обратного вызова. Этот обратный вызов принимает два параметра: разрешить и отклонить. Внутри обратного вызова мы можем делать другие вещи, если нам нужно. Если случай, который мы проверяем, работает, мы вызываем метод resolve, а если возникает проблема, мы вызываем метод reject.

Обещания всегда будут возвращать значение, что означает, что мы можем использовать метод .then(), чтобы реагировать на результаты обещания. .then() принимает два аргумента: обратный вызов в случае успеха и обратный вызов в случае сбоя. Мы можем соответствующим образом отреагировать на эти два состояния в телах двух обратных вызовов.

Использование - От сладкоежек до Bluetooth!

С этим новым пониманием обещаний давайте посмотрим, что происходит, когда мы подключаемся к устройству с помощью Web Bluetooth.

Web Bluetooth API предоставляет нам метод navigator.bluetooth.requestDevice(). Когда эта строка кода выполняется (обычно при нажатии кнопки), ваш браузер покажет меню сопряжения, чтобы ваш пользователь мог выбрать устройство для сопряжения. Когда пользователь выбирает устройство, щелкнув пункт меню, обещание будет разрешено, возвращая устройство, которое было сопряжено как объект с именем bluetoothDevice.

Мы можем использовать метод .then(), чтобы начать взаимодействие с этим устройством, например, если мы хотим вернуть имя устройства, мы могли бы вернуть свойство name.

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

function onButtonClick() {
 console.log('Requesting Bluetooth Device...');
 navigator.bluetooth.requestDevice(
   {filters: [{services: ['battery_service']}]})
 .then(device => {
   console.log('Connecting to GATT Server...');
   return device.gatt.connect();
 })
 .then(server => {
   console.log('Getting Battery Service...');
   return server.getPrimaryService('battery_service');
 })
 .then(service => {
   console.log('Getting Battery Level Characteristic...');
   return service.getCharacteristic('battery_level');
 })
 .then(characteristic => {
   console.log('Reading Battery Level...');
   return characteristic.readValue();
 })
 .then(value => {
   let batteryLevel = value.getUint8(0);
   console.log('Battery Level is ' + batteryLevel + '%');
 })
 .catch(error => {
   log('Something went wrong: ' + error);
 });

Я надеюсь, что это короткое и приятное введение в обещания поможет вам понять Web Bluetooth API. Если вы делаете что-то с помощью Web Bluetooth, я бы хотел их увидеть! Расскажите мне о своих проектах ниже или @thisisjofrank в твиттере!