Комментарии 18
Вообще-то в стандарте уже появился официальный способ отмены fetch:
https://developer.mozilla.org/en-US/docs/Web/API/AbortController
А вот и pull-request с добавлением этой функциональности в самый популярный полифилл:
https://github.com/github/fetch/pull/592
В вашем коде куча ошибок. Я, конечно, понимаю, что вы достиги дзена и можете интерпретировать код в голове. Но браузеры ещё не достигли вашего совершенства, чтобы выходить из бесконечного цикла за конечное время.
- Я тут переписал ваш код на файберах с отменяемыми запросами на любой стадии исполнения:
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( ',' )
}
Решил разобраться в вашей 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 в этом не помогает, я уж лучше на промисах как-нибудь, от них хотя бы понятно чего ждать
В bluebird есть возможность отмены уже много-много лет. Как и другие вещи, которых нет в нативных промисах. Как и скорость, превосходящая нативные промисы...
Как и скорость, превосходящая нативные промисы...
В этот момент всегда нужно приводить пруфлинк. Результаты в репозитории проекта устарели года на 3. Я сейчас на коленке собрал свой бенчмарк, и у меня нативные Promise выиграли (браузер Chrome, MacOS).
На абсолютную истину не претендую, просто заметил, что бенчмарки нужно регулярно пересматривать и лидеры могут меняться.
Дата обновления бенчмарка — март 2017.
Дата выхода используемой версии node.js — март 2017. Далеко не три года, как вы говорите — меньше года.
Или вот вам ещё более свежие бенчмарки прямо от ребят из nodejs. На turbofan всё равно быстрее оказывается bluebird, и их это огорчило.
Привет минусующим, берущим на веру утверждения о устаревших бенчмарках и свой бенчмарк с коленки. Почему этот бенчмарк некорректен — разбирать откровенно лень, мне за это деньги не платят.
Ну и так, чисто поржать — например, Promise.finally есть только в ES2018. А в bluebird я им уже много лет пользуюсь.
Или вот вам ещё более свежие бенчмарки прямо от ребят из nodejs.
Спасибо за интересную ссылку, много полезной информации. Несколько важных пунктов оттуда:
- Bluebird заточен чтобы побеждать в своем конкретном бенчмарке. Совпадает ли это с реальными use-case — неясно.
- Тем не менее разрыв между Bluebird и нативными промисами сокращается с каждой новой версией V8.
- Большинство пользователей сейчас пользуются 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, что не совсем корректно, но тем не менее.
Поэтому ничего смешного в том, что нативные промисы развиваются медленее, нет.
Смешно то, сколько лет потребовалось для введения промисов — и то они получились куцые и медленные. Несмотря на "учтение всего опыта". Ну и особенно смешно, что постоянно вижу агрессивную защиту и одновременно допиливание нативных промисов с одним единственным аргументом: "ну они же нативные!"
del
Спасибо за статью! В своё время пытылся решить проблему с недостатком отмены асинхронных действий в Promise. Разработал решение именуемое Deal — потомок Promise с встроенным медотом cancel и eventEmitter для взаимодействия с асинхронным процессом через Promise, которое находится в составе библиотеки Minimalist (https://github.com/mr-amirka/minimalist). Здесь метод cancel может отменять всю цепь и агрегацию из Promise. К сожалению, пока не хватает времени дописать всю документацию. Если кому-то действительно нужен отменяемый Promise, то рекоммендую обратить внимание.
Отменяемые Promises в EcmaScript6