Заметки за 2018 год (страница 2)

Отменяемые промисы

Промисы не имеют встроенного механизма отмены. Из-за этого разработчики придумывают различные хаки, которые порождают некоторые проблемы.

Дополнительный метод .cancel()

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

Отсутствие очистки после отмены

Некоторые используют Promise.race() в качестве механизма отмены. Однако, в этом случае у функции, создающей промис, нет возможности узнать, что произошла отмена и нужно освободить некоторые ресурсы.

speculation

Эрик Элиот написал маленький модуль speculation, который решает перечисленные выше проблемы.

Сигнатура функций:

speculation(fn: PromiseFunction, shouldCancel: Promise) => Promise

По аналогии с конструктором Promise , первым аргументом передается функция «исполнитель», а вторым аргументом передается промис для отмены.


PromiseFunction(resolve: Function, reject: Function, onCancel: Function) => Void

Функция-исполнитель принимает 3 аргумента и реализует логику разрешения, отклонения и отмены. Вот почему у самого промиса не нужен дополнительный метод .cancel() , и при этом вся логика, связанная с отменой, реализована внутри функции, которая создаёт этот промис.


import speculation from 'speculation';

function doWork(cancel = Promise.reject()) {
  speculation(
    (resolve, reject, onCancel) => {
      // делаем вычисления, которые завершаем вызовом resolve()

      onCancel(() => {
        // отменяем вычисления и освобождаем ресурсы
        reject(new Error('Cancelled'));
      });
    },
    cancel
  );
}

Отменённый промис получает статус rejected.

AbortController и AbortSignal

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


const controller = new AbortController();
const signal = controller.signal;

const request = fetch(url, {signal});

// запрос можно отменить, если необходимо
controller.abort();

Значение signal.aborted меняется только из контроллера. Одновременно с этим испускается событие abort . В спецификации дан пример, как можно применять AbortController и AbortSignal для своих API.

Отменённый промис так же получает статус rejected, а значение устанавливается в new DOMException('Aborted', 'AbortError').

Оставте свой комментарий

Автоматический вход на сайт с использованием Credential Management API

Современные браузеры предоставляют возможность пользователям сохранять пароль, когда пользователь входит на сайт. После этого сохранённые логин и пароль автоматически подставляется в форму входа, когда пользователь возвращается на сайт. Для сайта этот процесс проходит абсолютно незаметно. Однако автозаполнение не всегда работает правильно.

Credential Management API позволяет разработчикам сайта сохранять и получать данные для аутентификации. Обязательным условием работы этого API является безопасное соединение с сервером.


if (window.PasswordCredential) {
  navigator.credentials
    .get({
      password: true,
      mediation: 'optional'
    })
    .then(credential => {
      if (!credential) {
        return;
      }

      console.log(`Username: ${credential.id}`);
      console.log(`Password: ${credential.password}`);
    });
}

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

Свойство mediation у параметра метода .get() определяет сценарий запроса подтверждения у пользователя. Допустимые значения:

  • required — браузер всегда будет запрашивать разрешение;
  • optional — браузер будет запрашивать разрешение, если пользователь до этого момента не дал его;
  • silent — браузер не будет явно запрашивать разрешение.

Sign in with your account saved with Google Chrome

Чтобы отозвать ранее выданное разрешение (например, когда пользователь выходит из системы) нужно вызвать метод .preventSilentAccess().


if (navigator.credentials && navigator.credentials.preventSilentAccess) {
  navigator.credentials.preventSilentAccess();
}
Комментарии к заметке: 2

Способы создания реактивного потока данных

В реактивном программировании класс Observable является базовым и все манипуляции, как правило, происходят с его экземплярами.


const observable = Rx.Observable.create(
  function subscribe(observer) {
    observer.next(1);
    observer.next(2);
    observer.next(3);
    observer.complete();
  }
);

observable.subscribe(n => console.log(n));

В этом примере данные генерируются только внутри функции subscribe . Повлиять на поток данных извне никак нельзя. Чтобы управлять потоком данных, объект должен реализовывать методы класса Observer, т.е. методы next() , error() и complete().

Когда нужно генерировать данные в другом месте кода, то чаще всего используют класс Subject. Он представляет собой комбинацию Observable и Observer.


const subject = new Rx.Subject();

subject.subscribe(n => console.log(n));

subject.next(1);
subject.next(2);
subject.next(3);
subject.complete();

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


const delta$ = new Rx.Subject();

function handleNextTrack() {
  delta$.next(1);
}
function handlePrevTrack() {
  delta$.next(-1);
}

const playlist = (
  <Playlist
    onNextTrack={handleNextTrack}
    onPrevTrack={handlePrevTrack}
  />
);

Экземпляр класса Subject уведомляет своих подписчиков только когда появляются новые данные. Это напоминает как работают события — если вы подписались после того как событие произошло, то уведомление вы получите лишь в момент наступления следующего события. По этой причине для экземпляров Subject бесполезно применять операторы типа .startWith().

В RxJS есть специальный класс BehaviorSubject . Экземпляр этого класса хранит последнее событие и как только появляется очередной подписчик, он сразу получает последнее значение. На примере плейлиста, поток направления воспроизведения — это экземпляр Subject, а поток позиций в плейлисте — это экземпляр BehaviorSubject.

С другой стороны, поток позиции может быть получен из потока направления через свёртку методом .scan().


const position$ = Rx.Observable.from(delta$)
  .startWith(0)
  .scan((position, delta) => position + delta);
Оставте свой комментарий