request(options)
Popis
Provede XHR (aka AJAX) požadavky a vrací promise.
m.request({
method: 'PUT',
url: '/api/v1/users/:id',
params: { id: 1 },
body: { name: 'test' },
}).then(function (result) {
console.log(result);
});
Signatura
promise = m.request(options)
Argument | Type | Required | Description |
---|---|---|---|
options | Object | Yes | Konfigurační objekt pro požadavek. |
options.method | String | No | HTTP metoda, která se má použít. Měla by to být jedna z následujících hodnot: GET , POST , PUT , PATCH , DELETE , HEAD nebo OPTIONS . Výchozí hodnota je GET . |
options.url | String | Yes | URL adresa, na kterou se má požadavek odeslat. Může obsahovat proměnné, které se nahradí hodnotami z options.params . |
options.params | Object | No | Data pro interpolaci do URL a/nebo serializaci do dotazovacího řetězce. |
options.body | Object | No | Data pro serializaci do těla požadavku. |
options.async | Boolean | No | Určuje, zda je požadavek asynchronní. Výchozí hodnota je true . |
options.user | String | No | Uživatelské jméno pro HTTP autorizaci. Výchozí hodnota je undefined . |
options.password | String | No | Heslo pro HTTP autentizaci. Výchozí hodnota je undefined . Tato možnost je poskytována pro kompatibilitu s XMLHttpRequest . Kvůli bezpečnosti se doporučuje se jí vyhnout, protože odesílá heslo nešifrované. |
options.withCredentials | Boolean | No | Určuje, zda se mají odesílat cookies do domén třetích stran (cross-origin requests). Výchozí hodnota je false |
options.timeout | Number | No | Doba v milisekundách, po kterou může požadavek trvat, než bude automaticky ukončen. Výchozí hodnota je undefined . |
options.responseType | String | No | Očekávaný typ odpovědi. Pokud není definováno extract , výchozí hodnota je "json" . Jinak je výchozí hodnota "" . Pokud je responseType: "json" , interně provede JSON.parse(responseText) . |
options.config | xhr = Function(xhr) | No | Umožňuje přístup k internímu objektu XMLHttpRequest pro pokročilou konfiguraci. Můžete ho i nahradit vlastním objektem XHR. |
options.headers | Object | No | Hlavičky, které se připojí k požadavku před jeho odesláním (aplikují se těsně před options.config ). |
options.type | any = Function(any) | No | Konstruktor pro každý objekt v odpovědi. Výchozí hodnota je identita funkce. |
options.serialize | string = Function(any) | No | Funkce, která se použije pro serializaci dat v body . Výchozí hodnota je JSON.stringify , nebo pokud je options.body instancí FormData nebo URLSearchParams , výchozí hodnota je identita funkce (tj. function(value) {return value} ). |
options.deserialize | any = Function(any) | No | Funkce, která se použije pro deserializaci dat z xhr.response nebo xhr.responseText . Výchozí hodnota je identita funkce. Pokud je definován extract , deserialize bude přeskočen. |
options.extract | any = Function(xhr, options) | No | Funkce, která určuje, jak se má zpracovat odpověď XMLHttpRequest. Standardně tato funkce vrací výsledek options.deserialize(parsedResponse) . Pokud server vrátí chybový kód nebo je odpověď neplatná, funkce vyvolá výjimku. Pokud je poskytnut vlastní callback extract , parametr xhr je instance XMLHttpRequest použitá pro požadavek a options je objekt, který byl předán volání m.request . Kromě toho bude deserialize přeskočen a hodnota vrácená z callbacku extract bude ponechána tak, jak je, když se promise vyřeší. |
options.background | Boolean | No | Pokud je false , po dokončení požadavku se překreslí komponenty. Pokud je true , překreslení se neprovede. Výchozí hodnota je false . |
returns | Promise | Vrací promise, která se vyřeší s daty odpovědi po aplikaci funkcí extract , deserialize a type . Pokud stavový kód odpovědi indikuje chybu, promise se odmítne, ale tomu lze zabránit nastavením možnosti extract . |
promise = m.request(url, options)
Argument | Type | Required | Description |
---|---|---|---|
url | String | Yes | Název cesty, na kterou se má požadavek odeslat. options.url má přednost, pokud je přítomen. |
options | Object | No | Konfigurační objekt pro požadavek. |
returns | Promise | Promise, která se vyřeší s daty odpovědi po aplikaci funkcí extract , deserialize a type |
Tato druhá forma je většinou ekvivalentní m.request(Object.assign({url: url}, options))
, jen interně nezávisí na globálním ES6 Object.assign
.
Jak to funguje
m.request
je zjednodušené rozhraní pro XMLHttpRequest
, které umožňuje posílat HTTP požadavky na servery a získávat data.
m.request({
method: 'GET',
url: '/api/v1/users',
}).then(function (users) {
console.log(users);
});
Volání m.request
vrací promise a spustí překreslení po dokončení svého promise řetězce.
Ve výchozím nastavení m.request
předpokládá, že odpověď je ve formátu JSON a parsuje ji do objektu JavaScript (nebo pole).
Pokud stavový kód HTTP odpovědi indikuje chybu, vrácená promise bude odmítnuta. Poskytnutí callbacku extract zabrání odmítnutí promise.
Typické použití
Následující příklad ukazuje, jak komponenta používá m.request
k načtení dat ze serveru.
var Data = {
todos: {
list: [],
fetch: function () {
m.request({
method: 'GET',
url: '/api/v1/todos',
}).then(function (items) {
Data.todos.list = items;
});
},
},
};
var Todos = {
oninit: Data.todos.fetch,
view: function (vnode) {
return Data.todos.list.map(function (item) {
return m('div', item.title);
});
},
};
m.route(document.body, '/', {
'/': Todos,
});
Předpokládejme, že požadavek na serverovou adresu /api/items
vrací pole objektů ve formátu JSON.
Když se na konci volá funkce m.route
, inicializuje se komponenta Todos
. Volá se oninit
, která volá m.request
. Tím se asynchronně načte pole objektů ze serveru. "Asynchronně" znamená, že JavaScript pokračuje v běhu jiného kódu, zatímco čeká na odpověď ze serveru. V tomto případě to znamená, že se fetch
vrátí a komponenta se vykreslí pomocí původního prázdného pole jako Data.todos.list
. Jakmile se požadavek na server dokončí, pole objektů items
se přiřadí k Data.todos.list
a komponenta se znovu vykreslí, čímž se získá seznam <div>
s obsahujících názvy každého todo
.
Zpracování chyb
Pokud požadavek (kromě file:
protokolu) vrátí stavový kód mimo rozsah 2xx nebo 304, promise se odmítne s chybou. Tato chyba je běžná instance Error, ale s několika speciálními vlastnostmi.
error.message
je nastavena na surový text odpovědi.error.code
je nastavena na samotný stavový kód.error.response
je nastavena na parsovanou odpověď pomocíoptions.extract
aoptions.deserialize
, jako se to děje u normálních odpovědí.
To je užitečné v mnoha případech, kdy jsou chyby samy o sobě věci, se kterými se můžete vypořádat. Pokud chcete zjistit, zda relace vypršela - můžete udělat if (error.code === 401) return promptForAuth().then(retry)
. Pokud narazíte na mechanismus omezování API a vrátilo to chybu s "timeout": 1000
, můžete udělat setTimeout(retry, error.response.timeout)
.
Ikony načítání a chybové zprávy
Tento příklad rozšiřuje předchozí ukázku o indikátor načítání a zobrazení chybových zpráv:
var Data = {
todos: {
list: null,
error: '',
fetch: function () {
m.request({
method: 'GET',
url: '/api/v1/todos',
})
.then(function (items) {
Data.todos.list = items;
})
.catch(function (e) {
Data.todos.error = e.message;
});
},
},
};
var Todos = {
oninit: Data.todos.fetch,
view: function (vnode) {
return Data.todos.error
? [m('.error', Data.todos.error)]
: Data.todos.list
? [
Data.todos.list.map(function (item) {
return m('div', item.title);
}),
]
: m('.loading-icon');
},
};
m.route(document.body, '/', {
'/': Todos,
});
Existuje několik rozdílů mezi tímto a předchozím příkladem. Na začátku je Data.todos.list
null
. Také existuje další pole error
pro uložení chybové zprávy a pohled komponenty Todos
byl upraven tak, aby zobrazoval chybovou zprávu, pokud existuje, nebo zobrazoval ikonu načítání, pokud Data.todos.list
není pole.
Dynamické URL
URL adresy požadavků mohou obsahovat proměnné:
m.request({
method: 'GET',
url: '/api/v1/users/:id',
params: { id: 123 },
}).then(function (user) {
console.log(user.id); // logs 123
});
V kódu výše je :id
vyplněno daty z objektu params
a požadavek se stane GET /api/v1/users/123
.
Interpolace jsou ignorovány, pokud v property params
neexistují žádná odpovídající data.
m.request({
method: 'GET',
url: '/api/v1/users/foo:bar',
params: { id: 123 },
});
V kódu výše se požadavek stane GET /api/v1/users/foo:bar?id=123
Přerušení požadavků
V některých situacích je potřeba požadavek zrušit. Například u našeptávače chceme zajistit, aby se zpracoval jen poslední požadavek. Při psaní uživatel generuje mnoho požadavků, které se mohou dokončit v různém pořadí.
m.request()
zpřístupňuje svůj podkladový objekt XMLHttpRequest
prostřednictvím parametru options.config
, který vám umožňuje uložit odkaz na tento objekt a v případě potřeby zavolat jeho metodu abort
:
var searchXHR = null;
function search() {
abortPreviousSearch();
m.request({
method: 'GET',
url: '/api/v1/users',
params: { search: query },
config: function (xhr) {
searchXHR = xhr;
},
});
}
function abortPreviousSearch() {
if (searchXHR !== null) searchXHR.abort();
searchXHR = null;
}
Nahrávání souborů
Pro nahrávání souborů je potřeba získat odkaz na objekt File
. Nejjednodušší způsob je použít element <input type="file">
.
m.render(document.body, [m('input[type=file]', { onchange: upload })]);
function upload(e) {
var file = e.target.files[0];
}
Následující kód vytvoří formulářový prvek pro výběr souboru. Po výběru souboru uživatelem se spustí událost onchange
a zavolá se funkce upload
. e.target.files
obsahuje seznam vybraných souborů (objektů File
).
Dále je třeba vytvořit objekt FormData
pro vytvoření multipart request, což je speciálně formátovaný HTTP požadavek, který je schopen odesílat data souboru v těle požadavku.
function upload(e) {
var file = e.target.files[0];
var body = new FormData();
body.append('myfile', file);
}
Následně je třeba zavolat m.request
a nastavit options.method
na HTTP metodu, která používá tělo (např. POST
, PUT
, PATCH
) a použít objekt FormData
jako options.body
.
function upload(e) {
var file = e.target.files[0];
var body = new FormData();
body.append('myfile', file);
m.request({
method: 'POST',
url: '/api/v1/upload',
body: body,
});
}
Pokud je server nakonfigurován tak, aby přijímal multipart požadavky, budou informace o souboru spojeny s klíčem myfile
.
Nahrávání více souborů
Je možné nahrát více souborů najednou. Nahrávání proběhne atomicky: pokud se nahrávání alespoň jednoho souboru nezdaří, neuloží se žádný soubor. Pokud chcete zajistit uložení co největšího počtu souborů i při selhání sítě, nahrajte každý soubor zvlášť.
Chcete-li nahrát více souborů, jednoduše je všechny připojte k objektu FormData
. Při použití vstupu souboru můžete získat seznam souborů přidáním atributu multiple
do vstupu:
m.render(document.body, [
m('input[type=file][multiple]', { onchange: upload }),
]);
function upload(e) {
var files = e.target.files;
var body = new FormData();
for (var i = 0; i < files.length; i++) {
body.append('file' + i, files[i]);
}
m.request({
method: 'POST',
url: '/api/v1/upload',
body: body,
});
}
Monitorování průběhu
Při pomalých operacích (např. nahrávání velkých souborů) je vhodné zobrazit indikátor průběhu, aby uživatel věděl, že aplikace pracuje.
m.request()
zpřístupňuje svůj podkladový objekt XMLHttpRequest
prostřednictvím parametru options.config
, který vám umožňuje připojit posluchače událostí k objektu XMLHttpRequest:
var progress = 0;
m.mount(document.body, {
view: function () {
return [
m('input[type=file]', { onchange: upload }),
progress + '% completed',
];
},
});
function upload(e) {
var file = e.target.files[0];
var body = new FormData();
body.append('myfile', file);
m.request({
method: 'POST',
url: '/api/v1/upload',
body: body,
config: function (xhr) {
xhr.upload.addEventListener('progress', function (e) {
progress = e.loaded / e.total;
m.redraw(); // informuje Mithril.js, že se data změnila a je potřeba překreslení
});
},
});
}
Ve výše uvedeném příkladu je vykreslen vstup souboru. Pokud uživatel zvolí soubor, zahájí se nahrávání a v callbacku config
se zaregistruje obslužná rutina události progress
. Tato obslužná rutina události se spustí, kdykoli dojde k aktualizaci průběhu v XMLHttpRequest. Protože událost průběhu XMLHttpRequest neprobíhá v kontextu virtuálního DOM Mithril.js, je nutné zavolat m.redraw()
, aby se zajistilo překreslení komponenty.
Přetypování odpovědi na typ
V závislosti na architektuře aplikace je někdy vhodné transformovat data z odpovědi na konkrétní třídu nebo typ (například pro jednotné zpracování dat nebo pro přidání pomocných metod).
Můžete předat konstruktor jako parametr options.type
a Mithril.js jej vytvoří pro každý objekt v HTTP odpovědi.
function User(data) {
this.name = data.firstName + ' ' + data.lastName;
}
m.request({
method: 'GET',
url: '/api/v1/users',
type: User,
}).then(function (users) {
console.log(users[0].name); // logs a name
});
Ve výše uvedeném příkladu, za předpokladu, že /api/v1/users
vrací pole objektů, bude konstruktor User
vytvořen (tj. volán jako new User(data)
) pro každý objekt v poli. Pokud odpověď vrátila jeden objekt, byl by tento objekt použit jako argument body
.
Odpovědi, které nejsou JSON
Někdy server nevrací JSON, ale například HTML, SVG nebo CSV. Standardně se Mithril.js pokusí interpretovat odpověď jako JSON. Chcete-li toto chování přepsat, definujte vlastní funkci options.deserialize
:
m.request({
method: 'GET',
url: '/files/icon.svg',
deserialize: function (value) {
return value;
},
}).then(function (svg) {
m.render(document.body, m.trust(svg));
});
Ve výše uvedeném příkladu požadavek načte soubor SVG, neprovede nic pro jeho parsování (protože deserialize
pouze vrací hodnotu tak, jak je), a poté vykreslí řetězec SVG jako důvěryhodný HTML.
Samozřejmě, funkce deserialize
může být složitější:
m.request({
method: 'GET',
url: '/files/data.csv',
deserialize: parseCSV,
}).then(function (data) {
console.log(data);
});
function parseCSV(data) {
// naivní implementace pro zjednodušení příkladu
return data.split('\n').map(function (row) {
return row.split(',');
});
}
Ignorujeme-li skutečnost, že funkce parseCSV výše nezpracovává mnoho případů, které by správný parser CSV zpracovával, kód výše protokoluje pole polí.
V tomto ohledu mohou být užitečné i vlastní hlavičky. Například, pokud požadujete SVG, pravděpodobně chcete odpovídajícím způsobem nastavit typ obsahu. Chcete-li přepsat výchozí typ požadavku JSON, nastavte options.headers
na objekt párů klíč-hodnota odpovídajících názvům a hodnotám hlaviček požadavku.
m.request({
method: 'GET',
url: '/files/image.svg',
headers: {
'Content-Type': 'image/svg+xml; charset=utf-8',
Accept: 'image/svg, text/*',
},
deserialize: function (value) {
return value;
},
});
Načítání podrobností odpovědi
Standardně Mithril.js parsuje xhr.responseText
jako JSON a vrací výsledek. Někdy je potřeba získat podrobnější informace o odpovědi serveru a zpracovat ji ručně. Toho lze dosáhnout předáním vlastní funkce options.extract
:
m.request({
method: 'GET',
url: '/api/v1/users',
extract: function (xhr) {
return { status: xhr.status, body: xhr.responseText };
},
}).then(function (response) {
console.log(response.status, response.body);
});
Parametrem pro options.extract
je objekt XMLHttpRequest po dokončení jeho operace, ale předtím, než byl předán do vráceného promise řetězce, takže promise může stále skončit v odmítnutém stavu, pokud zpracování vyvolá výjimku.
Vydávání fetchů na IP adresy
Kvůli způsobu, jakým se v URL adresách detekují parametry, dochází k chybné interpretaci segmentů IPv6 adres jako proměnných. To vede k chybě.
// Toto nefunguje
m.request('http://[2001:db8::990a:cd27:4d9e:79]:8080/some/path', {
// ...
});
Chcete-li to obejít, měli byste předat dvojici adresa IPv6 + port jako parametr.
m.request('http://:host/some/path', {
params: { host: '[2001:db8::990a:cd27:4d9e:79]:8080' },
// ...
});
Toto není problém s adresami IPv4 a můžete je používat normálně.
// Toto bude fungovat, jak očekáváte
m.request('http://192.0.2.15:8080/some/path', {
// ...
});
Proč JSON místo HTML
Mnoho serverových frameworků používá šablonovací systém, který kombinuje data z databáze se šablonou a vytvoří HTML (při načítání stránky nebo pomocí AJAX). Pro interakci s uživatelem se pak používá jQuery.
Mithril.js je framework pro aplikace s bohatým uživatelským rozhraním, kde se šablony a data stahují odděleně a kombinují se v prohlížeči pomocí JavaScriptu. Zpracování šablon v prohlížeči snižuje zátěž serveru a tím i provozní náklady. Oddělení šablon a dat umožňuje efektivnější ukládání šablon do mezipaměti a jejich opětovné použití na různých typech zařízení (např. počítače, mobilní telefony). Mithril.js používá retained mode paradigma, které zjednodušuje vývoj a údržbu komplexních uživatelských rozhraní.
Ve výchozím nastavení m.request
očekává, že data odpovědi budou ve formátu JSON. V typické aplikaci Mithril.js jsou tato data JSON obvykle spotřebována pohledem.
Měli byste se vyhnout pokusům o vykreslení dynamického HTML generovaného serverem pomocí Mithril. Pokud máte aplikaci, která používá serverový systém šablon a chcete ji pře-architekturovat, nejprve se rozhodněte, zda je toto úsilí vůbec proveditelné. Migrace z architektury tlustého serveru na architekturu tlustého klienta je obvykle poněkud velké úsilí a zahrnuje refaktorování logiky ze šablon do logických datových služeb (a testování, které s tím souvisí).
Datové služby mohou být organizovány mnoha různými způsoby v závislosti na povaze aplikace. RESTful architektury jsou populární u poskytovatelů API a service oriented architectures jsou často vyžadovány tam, kde existuje mnoho vysoce transakčních pracovních postupů.
Proč XHR místo fetch
fetch()
je novější Web API pro načítání zdrojů ze serverů, podobné XMLHttpRequest
.
m.request
v Mithril.js používá XMLHttpRequest
místo fetch()
z následujících důvodů:
fetch
ještě není plně standardizované a jeho specifikace se může změnit.- Požadavky
XMLHttpRequest
lze zrušit před dokončením (např. pro zamezení problémů s konkurenčním přístupem u našeptávačů). XMLHttpRequest
poskytuje možnosti sledování průběhu pro dlouhotrvající požadavky (např. nahrávání souborů).XMLHttpRequest
je podporován všemi prohlížeči, zatímcofetch()
není podporován Internet Explorerem a starším Androidem (před 5.0 Lollipop).
V současnosti, kvůli nedostatku podpory prohlížeče, fetch()
obvykle vyžaduje polyfill, který má více než 11 kb nekomprimovaný - téměř třikrát větší než modul XHR Mithril.js.
I přes svou malou velikost podporuje XHR modul v Mithril.js důležité funkce jako interpolace URL a serializace parametrů v URL. Navíc se snadno integruje do systému automatického překreslování Mithril.js. Polyfill fetch
nepodporuje žádnou z těchto funkcí a vyžaduje další knihovny a boilerplate pro dosažení stejné úrovně funkčnosti.
Kromě toho je modul XHR Mithril.js optimalizován pro endpointy založené na JSON a činí tento nejběžnější případ vhodně stručným - tj. m.request(url)
- zatímco fetch
vyžaduje další explicitní krok k parsování dat odpovědi jako JSON: fetch(url).then(function(response) {return response.json()})
API fetch()
má v některých specifických případech technické výhody oproti XMLHttpRequest
:
- nabízí streamovací API (podobné streamování videa, ne reaktivnímu programování), které umožňuje snížit latenci a spotřebu paměti u velmi velkých odpovědí (za cenu složitějšího kódu).
- integruje se do Service Worker API, které poskytuje další vrstvu kontroly nad tím, jak a kdy dochází k síťovým požadavkům. Toto API také umožňuje přístup k push notifikacím a funkcím synchronizace na pozadí.
Ve většině případů streamování nepřináší výrazné zvýšení výkonu, protože stahování velkého objemu dat (megabajty) není optimální. Také zisky paměti z opakovaného opětovného použití malých bufferů mohou být kompenzovány nebo zrušeny, pokud vedou k nadměrnému překreslování prohlížeče. Z těchto důvodů se doporučuje volit streamování fetch()
místo m.request
pouze pro aplikace extrémně náročné na zdroje.
Vyhněte se anti-vzorům
Promise nejsou data odpovědi
m.request
vrací promise, ne data z odpovědi. Data nemůže vrátit přímo, protože HTTP požadavek může trvat dlouho (kvůli latenci sítě). Pokud by JavaScript čekal na dokončení požadavku, aplikace by se zablokovala.
// VYHNOUT SE
var users = m.request('/api/v1/users');
console.log('list of users:', users);
// `users` NENÍ seznam uživatelů, ale *promise*
// PREFEROVAT
m.request('/api/v1/users').then(function (users) {
console.log('list of users:', users);
});