Skip to content

Instantly share code, notes, and snippets.

@dvmn-tasks
Last active January 4, 2023 18:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save dvmn-tasks/f3d20e6b1e021203be80d170ea60ae9d to your computer and use it in GitHub Desktop.
Save dvmn-tasks/f3d20e6b1e021203be80d170ea60ae9d to your computer and use it in GitHub Desktop.
Простая JS-библиотека для использования в браузере. Предоставляет простые инструменты, похожие на Requests в Python, но с async/await

Аналог Requests для браузерного JS

Простая JS-библиотека для использования в браузере. Предоставляет простые инструменты, похожие на Requests в Python, но строго асинхронный с async и await.

Библиотека поставляется в виде нативного ECMAScript модуля. Подключать его следует в локальный контекст, а не как обычно в глобальный, подобно jQuery и прочим библиотекам. Туториал по модулям.

Библиотека заранее слегка адаптирована под использование с Django. Для этого она умеет отправлять CSRF-токен, придерживаясь стандартный соглашений из документации к Django. Дока.

Пример использования:

<script type="module">
  import {getJson} from "/static/async-fetch.mjs";

  async function main(){
    console.log('Demo script launched!!!');
    const profile = await getJson('https://example.com/user/999/');
    console.log('User profile', profile);
  }

  main();
</script>

PS

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

Библиотека специально создана без использования сборщиков пакетов Webpack, Parcel и подобных, чтобы максимально упростить подключение в проекты с простым фронтендом внутрь HTML-разметки.

Библиотека предназначена только для использования в современных браузерах с нативной поддержкой async, await, Promise, классов, spread- и rest- операторов.

export class HTTPError extends Error {
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error
// https://stackoverflow.com/a/31090384/4144498
constructor(status, ...params) {
super(...params);
// Maintains proper detailed stack trace, but it`s available only on V8
if (Error.captureStackTrace) {
Error.captureStackTrace(this, typeof(this));
}
this.name = this.constructor.name;
this.message = this.message || `HTTP ${status}.`;
this.status = status;
}
}
export class HTTPClientError extends HTTPError {
constructor(status, data, ...params) {
super(status, ...params);
this.data = data;
}
}
export async function handleJsonResponse(response){
if (response.status >= 400 && response.status < 500){
let data = await response.json();
throw new HTTPClientError(response.status, data, response.statusText);
}
if (!response.ok){
throw new HTTPError(response.status, response.statusText);
}
const responseContent = await response.json();
// Usually server send {"status": "OK"} inside JSON response.
// But this attribute duplicate HTTP Status code so can stay ignored.
// Besides not every API endpoint include {"status": "OK"} into JSON response
// so let`s ignore it for simplicity.
return responseContent;
}
export async function getJson(url, params={}) {
// Can throw HTTPError, HTTPClientError or network errors
// urlencode request params
let [baseUrl, query=''] = url.split('?');
const queryParams = new URLSearchParams(query);
const normalizedExtraParams = new URLSearchParams(params);
let compositeParams = new URLSearchParams([
...queryParams.entries(),
...normalizedExtraParams.entries(),
]);
let response = await fetch(baseUrl + '?' + compositeParams.toString(), {
method: 'GET',
credentials: 'include', // required by CORS during development on localhost, does not affect prod
headers: {
// Disabled because of conflict with simple CORS during development on localhost
// https://learn.javascript.ru/fetch-crossorigin#prostye-zaprosy
// 'X-Requested-With': 'XMLHttpRequest', // ask API for JSON response, not HTML
'Accept': 'application/json',
}
});
return await handleJsonResponse(response);
}
export async function postJson(url, data={}, csrfToken=null) {
// Can throw HTTPError, HTTPClientError or network errors
csrfToken = csrfToken || window.csrftoken;
let response = await fetch(url, {
method: 'POST',
credentials: 'include', // required by CORS during development on localhost, does not affect prod
body: new URLSearchParams(data),
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Accept': 'application/json',
'X-CSRFToken': csrfToken, // Django-specific code. See https://docs.djangoproject.com/en/3.2/ref/csrf/
}
});
return await handleJsonResponse(response);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment