Как стать автором
Обновить

Комментарии 18

Вот поэтому разработчики, если мне не изменяет память, Axios, под капотом используют вместо fetch старый добрый XMLHttpRequest

А ещё с fetch нельзя реализовать индикатор загрузки файла на сервер (upload progress). Поэтому убийцей XMLHttpRequest его никак не назвать.

Можно уточнить, при отмене Promise что должно происходить с ожидающим его onRejected? Раз мы говорим об интерфейсном соглашении, надо договориться о какой-то осмысленной ошибке.

Вообще-то в стандарте уже появился официальный способ отмены fetch:
https://developer.mozilla.org/en-US/docs/Web/API/AbortController


А вот и pull-request с добавлением этой функциональности в самый популярный полифилл:
https://github.com/github/fetch/pull/592

  1. В вашем коде куча ошибок. Я, конечно, понимаю, что вы достиги дзена и можете интерпретировать код в голове. Но браузеры ещё не достигли вашего совершенства, чтобы выходить из бесконечного цикла за конечное время.


  2. Я тут переписал ваш код на файберах с отменяемыми запросами на любой стадии исполнения:

Заголовок спойлера
const log = document.getElementById( 'log' )

let fetching = null

const fetchInformation = ()=> {

  fetching = $mol_fiber_make( ()=> {
    log.innerText = 'Fetching...'
    const text = fetch({ uri : 'some.csv' }).responseText
    log.innerText = JSON.stringify( parseCSV( text ) )
  } )

  fetching.start()
}

const cancelFetch = ()=> {
  fetching.destructor()
  fetching = null
  log.innerText = 'Cancelled'
}

const fetch = ({ uri })=> {

  return $mol_fiber_async( back => {

    const xhr = new XMLHttpRequest()

    xhr.open( 'GET', uri ) // API endpoint URL with some big CSV database

    xhr.onload = back( () => {

      if( Math.floor( xhr.status / 100 ) !== 2 ) {
          throw new Error( xhr.statusText )
      }

      return xhr
    } )

    xhr.onerror = back( () => {
      throw new Error( xhr.statusText )
    } )

    xhr.send()

    return ()=> xhr.abort()

  } )

}

const parseCSV = $mol_fiber_func( text => {

  let lastDemileterIdx = 0
  let result = []

  do {

    const newIdx = $mol_fiber_sync( ()=> text.indexOf( '\n' , lastDemileterIdx ) )

    const row = $mol_fiber_sync( ()=> {
        log.innerText += '.'
        const end = newIdx > -1 ? newIdx : Infinity
        return parseCSVRow( text.substring( lastDemileterIdx , end ) )
    } )

    result.push( row )
    lastDemileterIdx = newIdx + 1

  }  while( lastDemileterIdx > 0 ) 

  return result
} )

/* Some function for row parsing which works very slow  */
const parseCSVRow = text => {

  for( const from = Date.now() ; Date.now() < from + 5 ; ) {}

  return text.split( ',' )
}

http://plnkr.co/edit/vPKpKKwIhALm1lUEIrKu?p=preview

Решил разобраться в вашей fibers магии и обнаружил, что $mol_fiber_make обязана быть чистой функцией:


let requestCount = 0;

const fetchInformation = () => {
  fetching = $mol_fiber_make(() => {
    requestCount++;
    log.innerText = requestCount;
    const text = fetch({ uri : 'some.csv' }).responseText;
  });
  fetching.start();
}

Такой код насчитает два запроса вместо одного.


С таким подходом далеко в продакшен пойти не получится. Одно неловкое движение, интеграция с посторонней библиотекой — и ваш код взрывается и вы даже не понимаете как.

Нет, функия должна быть идемпотентной, но не обязательно чистой. Неидемпотентные вызовы просто заворачиваются в $mol_fiber_sync и они становятся идемпотентными.
Дока с тестами тут: https://github.com/eigenmethod/mol/tree/master/fiber
Вообще весь $mol построен на той же концепции, так что в продакшене вполне нормально с нею живётся.


А ваш код должен выглядеть так:


let requestCount = 0;

const fetchInformation = () => {
  fetching = $mol_fiber_make(() => {
    $mol_fiber_sync( ()=> requestCount++ );
    log.innerText = requestCount;
    const text = fetch({ uri : 'some.csv' }).responseText;
  });
  fetching.start();
}

То, что есть решение — это хорошо, но не стоит забывать зачем все эти fiber-подобные решения появились — чтобы сделать работу с асинхронностью более удобной и компактной. Заворачивание в $mol_fiber_sync в этом не помогает, я уж лучше на промисах как-нибудь, от них хотя бы понятно чего ждать

Ситуаций, где необходим неидемпотентный код крайне мало. Соответственно, и $mol_fiber_sync вставлять приходится крайне редко. И то в основном для оборачивания тяжёлых функций, чтобы они не блокировали поток на долго.

В bluebird есть возможность отмены уже много-много лет. Как и другие вещи, которых нет в нативных промисах. Как и скорость, превосходящая нативные промисы...

Как и скорость, превосходящая нативные промисы...

В этот момент всегда нужно приводить пруфлинк. Результаты в репозитории проекта устарели года на 3. Я сейчас на коленке собрал свой бенчмарк, и у меня нативные Promise выиграли (браузер Chrome, MacOS).


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

Дата обновления бенчмарка — март 2017.
Дата выхода используемой версии node.js — март 2017. Далеко не три года, как вы говорите — меньше года.


Или вот вам ещё более свежие бенчмарки прямо от ребят из nodejs. На turbofan всё равно быстрее оказывается bluebird, и их это огорчило.


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


Ну и так, чисто поржать — например, Promise.finally есть только в ES2018. А в bluebird я им уже много лет пользуюсь.

Или вот вам ещё более свежие бенчмарки прямо от ребят из nodejs.

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


  1. Bluebird заточен чтобы побеждать в своем конкретном бенчмарке. Совпадает ли это с реальными use-case — неясно.
  2. Тем не менее разрыв между Bluebird и нативными промисами сокращается с каждой новой версией V8.
  3. Большинство пользователей сейчас пользуются async/await функциями, которые могут быть сильнее оптимизированы, чем цепочки .then(). В идеале нас будут ждать async/await функции с оверхедом как у синхронных.

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


Ну и так, чисто поржать — например, Promise.finally есть только в ES2018. А в bluebird я им уже много лет пользуюсь.

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


Добавить фичу в основу языка намного сложнее, нужно поддерживать обратную совместимость. Поэтому ничего смешного в том, что нативные промисы развиваются медленее, нет. Они учитывают опыт user-land библиотек, в том числе и Bluebird.

Bluebird заточен чтобы побеждать в своем конкретном бенчмарке. Совпадает ли это с реальными use-case — неясно.

Видимо, вы не очень внимательно читали ссылку — там приводилась и сразу же опровергалась эта гипотеза, и был сделан вывод, что блюбёрд просто сделан так, чтобы побеждать любые бенчмарки.


Тем не менее разрыв между Bluebird и нативными промисами сокращается с каждой новой версией V8.

Да, поэтому когда-нибудь в 2020 году, когда нативные таки обгонят bluebird по производительности и добавят всё нужное, я спокойно удалю его из своих проектов. Хотя, может это будет и 2030… До тех пор не вижу в этом смысла.


Добавить функцию в библиотеку намного проще, чем в стандарт.

Естественно проще. Но для этого и есть функции, помечаемые как экспериментальные. Более того, в стандарте ещё и breaking changes случаются так же, как и в библиотеках — навскидку могу сказать, что очень позабавило изменение crypto.md5 с аргументами по умолчанию. Это, правда, пример из node.js а не ES, что не совсем корректно, но тем не менее.


Поэтому ничего смешного в том, что нативные промисы развиваются медленее, нет.

Смешно то, сколько лет потребовалось для введения промисов — и то они получились куцые и медленные. Несмотря на "учтение всего опыта". Ну и особенно смешно, что постоянно вижу агрессивную защиту и одновременно допиливание нативных промисов с одним единственным аргументом: "ну они же нативные!"

Я тоже собирал свой бенч месяц назад. Прирост производительности Bluebird в тесте показал десятикратный. В боевой нагрузке тоже поехало быстрее (не в 10 раз, конечно, но заметно). Доберусь до домашнего компьютера — поделюсь.
Не кажется ли это действие попыткой угнаться за RxJs?

Спасибо за статью! В своё время пытылся решить проблему с недостатком отмены асинхронных действий в Promise. Разработал решение именуемое Deal — потомок Promise с встроенным медотом cancel и eventEmitter для взаимодействия с асинхронным процессом через Promise, которое находится в составе библиотеки Minimalist (https://github.com/mr-amirka/minimalist). Здесь метод cancel может отменять всю цепь и агрегацию из Promise. К сожалению, пока не хватает времени дописать всю документацию. Если кому-то действительно нужен отменяемый Promise, то рекоммендую обратить внимание.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий