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

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

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


Некоторые библиотеки гигантские (есть даже монстры с плагинами, плагины для выполнения HTTP запроса!), некоторые увлеклись модными технологиями и поддерживают мало браузеров. Моя функция оказалась работающей даже на 10-летних браузерах и компактней других, так как не включает кучу никому не нужного функционала. Это наверно беда многих open source проектов, что каждый добавляет в проект никому кроме него не нужную функцию, и получается монстр.


Кстати, промисы плохи тем, что там смешаны в кучу ошибки (ошибка получения данных с сервера) и исключения (обращение к несуществующей переменной). Нафига перехватывать обращение к несуществующей переменной и заворачивать в промис? Это недостаток в дизайне промисов. Нужно не перехватывать такие ошибки, а завершать программу.


Промисы спроектированы глупо, людьми, у которых фобия исключений. Ловить надо только то, что явно запрошено пользователем, и не перехватывать остальные типы исключений. Но у людей необъяснимая фобия, и они ловят все подряд. Видимо от страха, что их кривая поделка упадет.

Нафига перехватывать обращение к несуществующей переменной и заворачивать в промис? Это недостаток в дизайне промисов. Нужно не перехватывать такие ошибки, а завершать программу
Убивать весь сервис из-за какой-то мелкой ошибки? (:

Во-первых, тут речь о браузере, и убивается не "сервис", а лишь вызов функции обработчика событий. Во-вторых, а почему в синхронном коде ошибка приводит к выбросу исключения и завершению программы, а в асинхронном — нет? Это нелогично. В третьих, если у вас веб-сервис, то вы можете ловить эти непойманные исключения на более высоком уровне и показывать страницу 503 вместо того, чтобы завершать процесс сервера. Точно так же, как и в синхронном коде. А с промисами все получается через одно место — исключение ловится там, где оно никому не нужно.


В четвертых, если разработчик забыл обработать реджект, то исключение просто теряется. То есть информацию об ошибке никто не получает и все думают, что все в порядке.


Единственное, что нормализует ситуацию — это async/await. А без них получается ад, все работает нелогично. Вообще, асинхронный код это боль, и если кому-то кажется, что это не так, то скорее всего только потому, что он не видит всех граблей.


Ваш вопрос про "убивать сервис", кстати, намекает, что вы сами не понимаете, как правильно обрабатывать исключения. И разработчики многие не понимают, и вообще не проверяют случаи, когда промис реджектится. Слава богу, промисы доработали, чтобы в таком случае это несчастное потерянное исключение куда-то там логгировалось. Спасибо адекватному человеку. Но это тоже неправильно, так как у нас есть необработанное исключение, и оно должно завершать программу, а не оставлять ее в полусломанном состоянии работать дальше.


Я логику промисов не могу объяснить ничем, кроме фобии исключений. Видел еще объяснение, что логику для JS-промисов просто скопировали из кода на Си++, реализовывашего промисы внутри браузера, а его разработчики, понятно, боятся что их браузер будет падать.

Во-вторых, а почему в синхронном коде ошибка приводит к выбросу исключения и завершению программы, а в асинхронном — нет?

У вас какое-то свое представление о промисах.


Для начала если никто не сделал .catch, то промис завершится с Uncaught (in promise) в глобальном стеке и никуда ошибка не пропадает и нигде не теряется.


А так же ошибки в промисах ловятся именно там где и должны — тем, кто этот промис вызвал (точнее, кто вызвал функцию, вернувшую промис. Ну или выше по цепочке вызовов).


У промисов, конечно же, есть недостатки, один из главных — их нельзя отменять, а так же они не таскают с собой "контекст" (это создает проблемы как с адекватным, но не репрезетативным стек трейсом, так и, например, в nest.js нельзя в обработчике получить request из контекста, не передавая его напрямую. Хотя это вроде починили с https://nodejs.org/api/async_hooks.html). Часть из этих проблем решают стримы, но это уже прям большой шаг вперед.


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


P.S.


Во-вторых, а почему в синхронном коде ошибка приводит к выбросу исключения и завершению программы, а в асинхронном — нет?

Если вы в синхронном коде успели навесить обработчик на событие, то приложение продолжит реагировать на это событие. И, в свою очередь, если в промисе (асинхронном коде) вылетела ошибка, то прекратится вся дальнейшая обработка этого кода и приложение будет ожидать следующего события. Куда уж логичнее?

Я логику промисов не могу объяснить ничем, кроме фобии исключений

А как вы видите это?


try {
  runSomethingAsync();
} catch(err) {
  // I caught it
}

так? Если да, то у меня для вас плохие новости. Тут проблема уже не в синтаксисе и чувстве прекрасного. Тут дело в том как устроены программы в реальности во время выполнения.


Единственное, что нормализует ситуацию — это async/await

async/await это promises

try {
  await runSomethingAsync();
} catch(err) {
  // I caught it
}

Очевидно же, так. Нет?
Очевидно же, так. Нет?

Теперь стало совсем не очевидно. Если runSomethingAsync возвращает promise, то оно уже работает как вы хотите. Вам не обязательно писать catch(callback). В случае ошибки оно само перейдёт в catch-блок try-catch-а. И не важно руками этот promise сделан или посредством async-метода.


Собственно в чём проблема? :)

В случае ошибки оно само перейдёт в catch-блок try-catch-а.


эммм… вообще-то нет… и как я понял, именно об этом Вы говорили в комменте, на который я ответил… я запутался…

jsfiddle.net/0nakzhqr/3

Я тоже запутался. Давайте разбираться.


const run = () => Promise.reject('something has gone wrong');

const asyncFunc = async () => {
    try {
        await run();
    }
    catch(err) {
        console.log('I got it', err);
    }
};

asyncFunc(); 
// "I got it something has gone wrong"
// Promise {<resolved>: undefined}

Как видите catch поймал reject-ый Promise. Стало быть до тех пор пока await ловит reject-ые Promise ваш catch в async-методе срабатывает на ура. Именно из-за того, что он цепляется к promise.catch (автоматически).


Сломается это к примеру тут:


const runSomeOldApi = callback => setTimeout(
        () => callback(null, 'someresult'), 
        1000
    );

const run = () => new Promise((resolve, reject) => {
    runSomeOldApi((error, result) => {
        doSomethingWrongHereThatWillThrowAnError();

        if (error) reject(error);
        else resolve(result);
    })
});

// остальная часть кода прежняя

Теперь мы получим Promise {<pending>} и Uncaught ReferenceError. Причина: мы выполнили это в отрыве от контекста try-catch. В случае работы с async-await либо напрямую с promise-ми (.then + chains of promises) — оно проставляет try-catch за нас. И в итоге случайная ошибка не приводит к повисшим await-ам и необработанным ошибкам.


Посему я не понимаю вашего негодования по поводу синтаксиса промисов, и как вы его противопоставляете async-await (хотя это одно и то же + сахар). Тут нет никакой боязни try-catch-ей. Она то тут как раз не причём :)

Пожалуй соглашусь с тем, что как правило проще написать под себя маленькую обёртку в Promise над уже существующим http клиентом, чем брать библиотеку.
Но вот про то, что пятисотка от сервера и исключения из-за обращения к неопределенной переменной — это принципиально разное, не соглашусь. И то, и другое — ошибки выполнения, и те и другие нуждаются в обработке.
Сама идея промисов подразумевает, что у нас есть две цепочки выполнения кода, нормальная и обработки ошибок.
Это целый подход к потоку выполнения, и он заразен из-за асинхронной природы, т.к. расползается по всему коду — почти все места должны быть готовы к тому, что тебе из функции сразу придёт промис и нужно будет выстраивать цепочку выполнения для обработки результатов.


Здорово прочищает мозги статья Railway oriented programming, где примеры даны на F#, но во многом перекликаются с нынешней реализацией промисов.

НЛО прилетело и опубликовало эту надпись здесь
Теоретически, промисы-фьючи могут работать и в многопоточке

Таки не могут.

НЛО прилетело и опубликовало эту надпись здесь

Да, все писали обертку в 10 строчек. А потом понадобилось добавить обработку форм, потом картинок, данных, потом пред-обработчики, пост-обработчики, установка хедеров и так далее. В итоге получили свой axios, только хуже оттестированный.


Ловить надо только то, что явно запрошено пользователем, и не перехватывать остальные типы исключений

В js нет разницы между пользовательскими ошибками и программными. С чего механизм отлова ошибок в одном месте должен в таком случае отличаться от других? А ещё, интересно, в каком контексте по-вашему должны сыпаться ошибки вроде обращения к необъявленной переменной если я хочу обернуть ее и показывать пользователю хотя бы что-то вместо белого экрана? А ещё как разделить? Нет доступа к сети к чему относится? А закончилась память? А ublock заблокировал запрос?


PS для fetch никакой статус ответа не является ошибкой и это, в общем случае, правильно. Но не удобно.

Ну если общение с backend-ом сводится к тому чтобы гонять туда сюда JSON-ики и formData в одном и том же стиле и формате, то необходимость подключать axios так и не приходит. К тому же в любом большом приложении должна быть прослойка для работы с API, чтобы лишняя фигня вроде "установки хедеров" и "пост-обработчики" и "пред-обработчики" не пролазили в код. А написаны они поверх axios или поверх fetch, для большинства проектов, момент не принципиальный.


Я думаю axios всё таки больше актуален для шибко мутных проектов, в частности когда backend это сплошное слоёное legacy.

Ну Axios всё таки имеет куда больше функционала. Многим нужны инстансы клиентов и интерсепторы.
Много лет использовал axios как основную библиотеку для запросов из браузера. Но в последнем проекте решил, что тянуть огромную либу в проект только для запросов не разумно.

В итоге написал обертку class API над fetch. Все красиво завернул в async/await, плюс обработка ответа и перехват ошибок. В итоге вышло чуть более 100 строк кода на TS. Что совсем ничего, по сравнении в кодом axios.

Так что думаю, имеет смысл переходить на нативный функционал. Да, пришлось немного допилить, что бы вот совсем хорошо было. Но доработка минимальна.

PS Что касается загрузки файлов и показа прогресса. В данном кейсе пришлось написать обортку над XMLHttpRequest. Увы…
Спасибо за статью, хоть узнал что такое Axios.
A то всё по-старинке через XMLHttpRequest с колбеками :)
Недавно попробовал fetch — вроде работает отлично.
О том, что есть ещё какие-то другие способы даже не знал.
Спасибо, не знал о fetch.
Axios хотя и выглядит секси, но честно как-то жаба давит каждый раз подключая лищьнюю либу.
Хотя стоп. Не все так однозначно — теперь чтобы замокать fetch в тестах я не смогу юзать jest.mock, выход ребята нашли но опять же не сильно красивый
теперь чтобы замокать fetch в тестах

А вы мокайте не fetch а сами api-методы. В идеале fetch должен использоваться только в файлах, которые содержат инструментарий для взаимодействия с сервером. Пред и постобработка запросов, склеивание URL, возможно кеширование и многое другое. Это должно быть отдельной прослойкой в приложении (во всяком случае если оно большое). А в сам код импортируется уже готовый к использованию promise-based метод. Вот его и mock-ать :)

> теперь чтобы замокать fetch в тестах я не смогу юзать jest.mock

Потому что надо юзать jest.spyOn(global, 'fetch').mockResolvedValue(...) :)
НЛО прилетело и опубликовало эту надпись здесь
Ситуацию ухудшает и широко обсуждаемая уязвимость. Об этой уязвимости сообщено 2017 году. Авторы проекта игнорировали её более двух лет.

commented on Apr 15, 2018
merged 2 commits into axios:master from unknown repository on May 7
очень уж преувеличено 2 года -> 1 :)
Axios хорош из-за того, что при ssr какого-нибудь Nuxt.js не надо делить код на два варианта — для сервера и для клиента.

есть же isomorphic-fetch, например

В 2019 году вместо Promise стоит использовать async/await, если уж на то пошло
fetch не позволяет отслеживать прогресс запроса(аплоад\даунлоад файла), не знаю как с этим у axios, но думаю что всё хорошо. Хотя всё равно каждый раз пишу свою обёртку над нативными функциями, пока что так и не столкнулся с необходимостью использовать axios.

сравнения тоже довольно странные:
fetch("https://jsonplaceholder.typicode.com/posts", {
  method: "POST",
  body: JSON.stringify({
    title: "Title of post",
    body: "Post Body"
  })
})
  .then(res => {
    if (!response.ok) throw Error(response.statusText);
    return response.json();
  })


ничего не стоит обернуть это в функцию, не дублировать код и получить то же самое что и с axios.
axios поддерживает onUploadProgress
Если вы решите использовать в проекте fetch, предполагая что лучше использовать стандартный api чем какие то библиотеки, то может оказаться, что всё равно нужно использовать полифил для работы в некоторых браузерах (привет майкрософт) и ещё неприятнее осознавать, что в некоторых версиях браузеров поддержка fetch api хоть и есть, но не полная и из за наличия этой самой поддержки не применяется полифил.
Какая-то воистину хейтерская статья. Axios лучший на данный момент.
Axios убьёт только Axios2. Который под капотом будет использовать fetch. Не удивлюсь если команда Axios нечто подобное уже пилит.
fetch ведь даже не умеет трекать прогресс выгрузки данных на сервер и не может быть отменен (в axios отмена запроса конечно тоже та еще порнуха, но хоть как-то есть).
Раньше использовал axios, но последнее время superagent из-за намного более удобной отмены запроса, на прям совсем последнем проекте использую got (со стороны сервера), просто попробовать.
и не может быть отменен

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

Лучшее, что я видел, это rxjs ajax. Ну или еще лучше — ангуларовская (2+) обертка $http. Но стримы — то еще зло, поэтому в для небольших проектов, конечно, не подходит.

могу ошибаться, но Axios это отделившийся от Ангуляра $http

От первого? Просто начиная со второго (который я и имел ввиду), $http завязан на rxjs, а аксиос на промисах работает.

если сравнивать доки, то скорее от первого. прям очень сильно похожи.

Axios подкупает своим «Axios.get» и простым дизайном функции, а не вот этими трешовыми конфигами. Не помню точно, но вот с fetch какие-то мелкие косяки всё время убивали желание использовать его, т.к. по сути ничего толком от xmlhttprequest не изменилось. Так зачем тогда его использовать?
Так зачем тогда его использовать?

У fetch главное преимущество в том, что он весит 0 байт (без полифила). Если это для вашего проекта не аргумент, то да, вам fetch может быть не нужен. Просто очередное API, которое не подошло.

Весит 13КБ, что вообще очень мало. Можно и пренебречь. Больше расстраивает когда тащится фреймворк в 3МБ(Framework7), что дико напрягает, вот его желательно ручками бы сократить.
А для и сервера и браузера одновременно (ssr)?
Зарегистрируйтесь на Хабре, чтобы оставить комментарий