Lapisan akses data universal untuk aplikasi web.
Biasanya di server, Anda menghubungi API atau database Anda secara langsung untuk mengambil beberapa data. Namun, pada klien, Anda tidak selalu dapat menghubungi layanan Anda dengan cara yang sama (yaitu, kebijakan domain lintas). Sebaliknya, permintaan XHR/Fetch perlu dilakukan ke server yang diteruskan ke layanan Anda.
Harus menulis kode secara berbeda untuk kedua lingkungan adalah duplikat dan rawan kesalahan. Fetchr menyediakan lapisan abstraksi atas panggilan layanan data Anda sehingga Anda dapat mengambil data menggunakan API yang sama di server dan sisi klien.
npm install fetchr --save PENTING: Saat di browser, Fetchr bergantung sepenuhnya pada API Fetch . Jika Anda perlu mendukung browser lama, Anda harus memasang polyfill juga (mis. Https://github.com/github/fetch).
Ikuti langkah -langkah di bawah ini untuk mengatur fetchr dengan benar. Ini mengasumsikan Anda menggunakan kerangka kerja ekspres.
Di sisi server, tambahkan middleware fetchr ke dalam aplikasi Express Anda di titik akhir API khusus.
Fetchr Middleware mengharapkan bahwa Anda menggunakan middleware body-parser (atau middleware alternatif yang mengisi req.body ) sebelum Anda menggunakan fetchr middleware.
import express from 'express' ;
import Fetcher from 'fetchr' ;
import bodyParser from 'body-parser' ;
const app = express ( ) ;
// you need to use body-parser middleware before fetcher middleware
app . use ( bodyParser . json ( ) ) ;
app . use ( '/myCustomAPIEndpoint' , Fetcher . middleware ( ) ) ; Di sisi klien, perlu untuk opsi xhrPath untuk mencocokkan jalur di mana middleware dipasang pada langkah sebelumnya
xhrPath adalah properti konfigurasi opsional yang memungkinkan Anda untuk menyesuaikan titik akhir ke layanan Anda, default ke /api .
import Fetcher from 'fetchr' ;
const fetcher = new Fetcher ( {
xhrPath : '/myCustomAPIEndpoint' ,
} ) ; Anda perlu mendaftarkan layanan data apa pun yang ingin Anda gunakan dalam aplikasi Anda. Antarmuka untuk layanan Anda akan menjadi objek yang harus menentukan properti resource dan setidaknya satu operasi CRUD. Properti resource akan digunakan saat Anda memanggil salah satu operasi CRUD.
// app.js
import Fetcher from 'fetchr' ;
import myDataService from './dataService' ;
Fetcher . registerService ( myDataService ) ; // dataService.js
export default {
// resource is required
resource : 'data_service' ,
// at least one of the CRUD methods is required
read : async function ( { req , resource , params , config } ) {
return { data : 'foo' } ;
} ,
// other methods
// create: async function({ req, resource, params, body, config }) {},
// update: async function({ req, resource, params, body, config }) {},
// delete: async function({ req, resource, params, config }) {}
} ;Layanan data mungkin memerlukan akses ke setiap permintaan individu, misalnya, untuk mendapatkan sesi pengguna yang masuk saat ini. Untuk alasan ini, Fetcher harus dipakai sekali per permintaan.
Di server, ini mengharuskan Fetcher untuk dipakai per permintaan, di Middleware Express. Di sisi klien, ini hanya perlu terjadi pada pemuatan halaman.
// app.js - server
import express from 'express' ;
import Fetcher from 'fetchr' ;
import myDataService from './dataService' ;
const app = express ( ) ;
// register the service
Fetcher . registerService ( myDataService ) ;
// register the middleware
app . use ( '/myCustomAPIEndpoint' , Fetcher . middleware ( ) ) ;
app . use ( function ( req , res , next ) {
// instantiated fetcher with access to req object
const fetcher = new Fetcher ( {
xhrPath : '/myCustomAPIEndpoint' , // xhrPath will be ignored on the serverside fetcher instantiation
req : req ,
} ) ;
// perform read call to get data
fetcher
. read ( 'data_service' )
. params ( { id : 42 } )
. then ( ( { data , meta } ) => {
// handle data returned from data fetcher in this callback
} )
. catch ( ( err ) => {
// handle error
} ) ;
} ) ; // app.js - client
import Fetcher from 'fetchr' ;
const fetcher = new Fetcher ( {
xhrPath : '/myCustomAPIEndpoint' , // xhrPath is REQUIRED on the clientside fetcher instantiation
} ) ;
fetcher
. read ( 'data_api_fetcher' )
. params ( { id : 42 } )
. then ( ( { data , meta } ) => {
// handle data returned from data fetcher in this callback
} )
. catch ( ( err ) => {
// handle errors
} ) ;
// for create you can use the body() method to pass data
fetcher
. create ( 'data_api_create' )
. body ( { some : 'data' } )
. then ( ( { data , meta } ) => {
// handle data returned from data fetcher in this callback
} )
. catch ( ( err ) => {
// handle errors
} ) ; Lihat contoh sederhana.
Panggilan layanan pada klien secara transparan menjadi permintaan pengambilan. Merupakan ide bagus untuk mengatur header cache pada panggilan pengambilan umum. Anda dapat melakukannya dengan memberikan parameter ketiga dalam panggilan balik layanan Anda. Jika Anda ingin melihat header apa yang ditetapkan oleh layanan yang baru saja Anda panggil, cukup periksa parameter ketiga dalam panggilan balik.
Catatan: Jika Anda menggunakan janji, metadata akan tersedia di properti meta dari nilai yang diselesaikan.
// dataService.js
export default {
resource : 'data_service' ,
read : async function ( { req , resource , params , config } ) {
return {
data : 'response' , // business logic
meta : {
headers : {
'cache-control' : 'public, max-age=3600' ,
} ,
statusCode : 200 , // You can even provide a custom statusCode for the fetch response
} ,
} ;
} ,
} ; fetcher
. read ( 'data_service' )
. params ( { id : ### } )
. then ( ( { data , meta } ) {
// data will be 'response'
// meta will have the header and statusCode from above
} ) ; Ada metode kenyamanan yang disebut fetcher.getServiceMeta pada instance fetchr. Metode ini akan mengembalikan metadata untuk semua panggilan yang telah terjadi sejauh ini dalam format array. Di server, ini akan mencakup semua panggilan layanan untuk permintaan saat ini. Di klien, ini akan mencakup semua panggilan layanan untuk sesi saat ini.
Biasanya Anda membuat fetcher dengan beberapa opsi default untuk seluruh sesi browser, tetapi mungkin ada kasus di mana Anda ingin memperbarui opsi ini nanti di sesi yang sama.
Anda dapat melakukannya dengan metode updateOptions :
// Start
const fetcher = new Fetcher ( {
xhrPath : '/myCustomAPIEndpoint' ,
xhrTimeout : 2000 ,
} ) ;
// Later, you may want to update the xhrTimeout
fetcher . updateOptions ( {
xhrTimeout : 4000 ,
} ) ; Ketika kesalahan terjadi dalam metode fetchr crud Anda, Anda harus melempar objek kesalahan. Objek kesalahan harus berisi statusCode (default 500) dan properti output yang berisi objek serializable JSON yang akan dikirim ke klien.
export default {
resource : 'FooService' ,
read : async function create ( req , resource , params , configs ) {
const err = new Error ( 'it failed' ) ;
err . statusCode = 404 ;
err . output = { message : 'Not found' , more : 'meta data' } ;
err . meta = { foo : 'bar' } ;
throw err ;
} ,
} ;Dan dalam panggilan layanan Anda:
fetcher
. read ( 'someData' )
. params ( { id : '42' } )
. catch ( ( err ) => {
// err instanceof FetchrError -> true
// err.message -> "Not found"
// err.meta -> { foo: 'bar' }
// err.name = 'FetchrError'
// err.output -> { message: "Not found", more: "meta data" }
// err.rawRequest -> { headers: {}, method: 'GET', url: '/api/someData' }
// err.reason -> BAD_HTTP_STATUS | BAD_JSON | TIMEOUT | ABORT | UNKNOWN
// err.statusCode -> 404
// err.timeout -> 3000
// err.url -> '/api/someData'
} ) ; Objek dengan metode abort dikembalikan saat membuat permintaan fetchr pada klien. Ini berguna jika Anda ingin membatalkan permintaan sebelum selesai.
const req = fetcher
. read ( 'someData' )
. params ( { id : 42 } )
. catch ( ( err ) => {
// err.reason will be ABORT
} ) ;
req . abort ( ) ; xhrTimeout adalah properti konfigurasi opsional yang memungkinkan Anda mengatur batas waktu (dalam MS) untuk semua permintaan klien, default ke 3000 . Di sisi klien, Xhrpath dan Xhrtimeout akan digunakan untuk semua permintaan. Di server, Xhrpath dan Xhrtimeout tidak diperlukan dan diabaikan.
import Fetcher from 'fetchr' ;
const fetcher = new Fetcher ( {
xhrPath : '/myCustomAPIEndpoint' ,
xhrTimeout : 4000 ,
} ) ; Jika Anda ingin menetapkan batas waktu per permintaan, Anda dapat menghubungi clientConfig dengan properti timeout :
fetcher
. read ( 'someData' )
. params ( { id : 42 } )
. clientConfig ( { timeout : 5000 } ) // wait 5 seconds for this request before timing out
. catch ( ( err ) => {
// err.reason will be TIMEOUT
} ) ; Untuk beberapa aplikasi, mungkin ada situasi di mana Anda perlu memproses Params Layanan yang dilewati dalam permintaan sebelum dikirim ke layanan yang sebenarnya. Biasanya, Anda akan memprosesnya dalam layanan itu sendiri. Namun, jika Anda perlu melakukan pemrosesan di banyak layanan (yaitu sanitasi untuk keamanan), maka Anda dapat menggunakan opsi paramsProcessor .
paramsProcessor adalah fungsi yang diteruskan ke metode Fetcher.middleware . Ini disahkan tiga argumen, objek permintaan, objek serviceInfo, dan objek Layanan Params. Fungsi paramsProcessor kemudian dapat memodifikasi param layanan jika diperlukan.
Inilah contohnya:
/**
Using the app.js from above, you can modify the Fetcher.middleware
method to pass in the paramsProcessor function.
*/
app . use (
'/myCustomAPIEndpoint' ,
Fetcher . middleware ( {
paramsProcessor : function ( req , serviceInfo , params ) {
console . log ( serviceInfo . resource , serviceInfo . operation ) ;
return Object . assign ( { foo : 'fillDefaultValueForFoo' } , params ) ;
} ,
} ) ,
) ; Untuk beberapa aplikasi, mungkin ada situasi di mana Anda perlu memodifikasi respons sebelum diteruskan ke klien. Biasanya, Anda akan menerapkan modifikasi Anda di layanan itu sendiri. Namun, jika Anda perlu memodifikasi respons di banyak layanan (yaitu menambahkan informasi debug), maka Anda dapat menggunakan opsi responseFormatter .
responseFormatter adalah fungsi yang diteruskan ke metode Fetcher.middleware . Itu disahkan tiga argumen, objek permintaan, objek respons dan objek respons layanan (yaitu data yang dikembalikan dari layanan Anda). Fungsi responseFormatter kemudian dapat memodifikasi respons layanan untuk menambahkan informasi tambahan.
Lihatlah contoh di bawah ini:
/**
Using the app.js from above, you can modify the Fetcher.middleware
method to pass in the responseFormatter function.
*/
app . use (
'/myCustomAPIEndpoint' ,
Fetcher . middleware ( {
responseFormatter : function ( req , res , data ) {
data . debug = 'some debug information' ;
return data ;
} ,
} ) ,
) ; Sekarang ketika permintaan dilakukan, respons Anda akan berisi properti debug yang ditambahkan di atas.
Fetchr memberikan dukungan CORS dengan memungkinkan Anda untuk melewati host asal penuh ke opsi corsPath .
Misalnya:
import Fetcher from 'fetchr' ;
const fetcher = new Fetcher ( {
corsPath : 'http://www.foo.com' ,
xhrPath : '/fooProxy' ,
} ) ;
fetcher . read ( 'service' ) . params ( { foo : 1 } ) . clientConfig ( { cors : true } ) ; Selain itu, Anda juga dapat menyesuaikan bagaimana URL GET dibangun dengan menyerahkan properti constructGetUri saat Anda menjalankan panggilan read :
import qs from 'qs' ;
function customConstructGetUri ( uri , resource , params , config ) {
// this refers to the Fetcher object itself that this function is invoked with.
if ( config . cors ) {
return uri + '/' + resource + '?' + qs . stringify ( this . context ) ;
}
// Return `falsy` value will result in `fetcher` using its internal path construction instead.
}
import Fetcher from 'fetchr' ;
const fetcher = new Fetcher ( {
corsPath : 'http://www.foo.com' ,
xhrPath : '/fooProxy' ,
} ) ;
fetcher . read ( 'service' ) . params ( { foo : 1 } ) . clientConfig ( {
cors : true ,
constructGetUri : customConstructGetUri ,
} ) ; Anda dapat melindungi jalur middleware fetchr Anda dari serangan CSRF dengan menambahkan middleware di depannya:
app.use('/myCustomAPIEndpoint', csrf(), Fetcher.middleware());
Anda bisa menggunakan https://github.com/expressjs/csurf untuk ini sebagai contoh.
Selanjutnya Anda perlu memastikan bahwa token CSRF sedang dikirim dengan permintaan kami sehingga mereka dapat divalidasi. Untuk melakukan ini, lewati token sebagai kunci di objek options.context pada klien:
const fetcher = new Fetcher ( {
xhrPath : '/myCustomAPIEndpoint' , // xhrPath is REQUIRED on the clientside fetcher instantiation
context : {
// These context values are persisted with client calls as query params
_csrf : 'Ax89D94j' ,
} ,
} ) ; _csrf ini akan dikirim dalam semua permintaan klien sebagai parameter kueri sehingga dapat divalidasi di server.
Saat memanggil layanan fetcher, Anda dapat melewati objek konfigurasi opsional.
Ketika panggilan ini dilakukan dari klien, objek konfigurasi digunakan untuk mengatur beberapa opsi permintaan dan dapat digunakan untuk mengganti opsi default:
//app.js - client
const config = {
timeout : 6000 , // Timeout (in ms) for each request
unsafeAllowRetry : false , // for POST requests, whether to allow retrying this post
} ;
fetcher . read ( 'service' ) . params ( { id : 1 } ) . clientConfig ( config ) ;Untuk permintaan dari server, objek Config hanya diteruskan ke layanan yang dipanggil.
Anda dapat mengatur fetchr untuk secara otomatis mencoba kembali permintaan yang gagal dengan menentukan retry konfigurasi di global atau dalam konfigurasi permintaan:
// Globally
const fetchr = new Fetchr ( {
retry : { maxRetries : 2 } ,
} ) ;
// Per request
fetchr . read ( 'service' ) . clientConfig ( {
retry : { maxRetries : 1 } ,
} ) ; Dengan konfigurasi di atas, Fetchr akan mencoba lagi dua kali semua permintaan yang gagal tetapi hanya sekali saat menelepon read('service') .
Anda dapat lebih lanjut menyesuaikan cara kerja ulang mekanisme. Ini semua pengaturan dan nilai defaultnya:
const fetchr = new Fetchr ( {
retry : {
maxRetries : 2 , // amount of retries after the first failed request
interval : 200 , // maximum interval between each request in ms (see note below)
statusCodes : [ 0 , 408 ] , // response status code that triggers a retry (see note below)
} ,
unsafeAllowRetry : false , // allow unsafe operations to be retried (see note below)
}selang
Interval antara setiap permintaan menghormati formula berikut, berdasarkan backoff eksponensial dan strategi jitter penuh yang diterbitkan dalam posting blog arsitektur AWS ini:
Math . random ( ) * Math . pow ( 2 , attempt ) * interval ; attempt adalah jumlah upaya coba lagi saat ini mulai dari 0. interval default sesuai dengan 200 ms.
Kode Status
Untuk alasan historis, FetchR hanya mencoba kembali 408 tanggapan dan tidak ada tanggapan sama sekali (misalnya, kesalahan jaringan, ditunjukkan oleh kode status 0). Namun, Anda mungkin berguna untuk juga mencoba lagi pada kode lain juga (502, 503, 504 dapat menjadi kandidat yang baik untuk penitipan ulang otomatis).
UnsafeAllowretry
Secara default, Fetchr hanya mencoba kembali permintaan read . Ini dilakukan untuk alasan keamanan: Membaca dua kali entri dari database tidak seburuk membuat entri dua kali. Tetapi jika aplikasi atau sumber daya Anda tidak memerlukan perlindungan semacam ini, Anda dapat mengizinkan pengembalian ulang dengan menetapkan unsafeAllowRetry ke true dan Fetchr akan mencoba lagi semua operasi.
Secara default, Fetchr menambahkan semua nilai konteks ke URL permintaan sebagai params kueri. contextPicker memungkinkan Anda untuk memiliki kontrol yang lebih besar atas variabel konteks mana yang dikirim sebagai params kueri tergantung pada metode permintaan ( GET atau POST ). Ini berguna ketika Anda ingin membatasi jumlah variabel dalam URL GET agar tidak secara tidak sengaja cache bust.
contextPicker mengikuti format yang sama dengan parameter predicate dalam lodash/pickBy dengan dua argumen: (value, key) .
const fetcher = new Fetcher ( {
context : {
// These context values are persisted with client calls as query params
_csrf : 'Ax89D94j' ,
device : 'desktop' ,
} ,
contextPicker : {
GET : function ( value , key ) {
// for example, if you don't enable CSRF protection for GET, you are able to ignore it with the url
if ( key === '_csrf' ) {
return false ;
}
return true ;
} ,
// for other method e.g., POST, if you don't define the picker, it will pick the entire context object
} ,
} ) ;
const fetcher = new Fetcher ( {
context : {
// These context values are persisted with client calls as query params
_csrf : 'Ax89D94j' ,
device : 'desktop' ,
} ,
contextPicker : {
GET : [ 'device' ] , // predicate can be an array of strings
} ,
} ) ; Saat menelepon layanan fetcher, Anda dapat menambahkan header permintaan khusus.
Permintaan berisi header khusus saat Anda menambahkan opsi headers ke 'ClientConfig'.
const config = {
headers : {
'X-VERSION' : '1.0.0' ,
} ,
} ;
fetcher . read ( 'service' ) . params ( { id : 1 } ) . clientConfig ( config ) ; Semua permintaan berisi header khusus saat Anda menambahkan opsi headers untuk membuat argumen 'fetcher'.
import Fetcher from 'fetchr' ;
const fetcher = new Fetcher ( {
headers : {
'X-VERSION' : '1.0.0' ,
} ,
} ) ; Untuk mengumpulkan statistik keberhasilan/kegagalan/latensi layanan fetcher, Anda dapat mengkonfigurasi statsCollector untuk Fetchr . Fungsi statsCollector akan dipanggil dengan satu argumment: stats . Objek stats akan berisi bidang -bidang berikut:
create|read|update|delete import Fetcher from 'fetchr' ;
const fetcher = new Fetcher ( {
xhrPath : '/myCustomAPIEndpoint' ,
statsCollector : function ( stats ) {
// just console logging as a naive example. there is a lot more you can do here,
// like aggregating stats or filtering out stats you don't want to monitor
console . log (
'Request for resource' ,
stats . resource ,
'with' ,
stats . operation ,
'returned statusCode:' ,
stats . statusCode ,
' within' ,
stats . time ,
'ms' ,
) ;
} ,
} ) ; app . use (
'/myCustomAPIEndpoint' ,
Fetcher . middleware ( {
statsCollector : function ( stats ) {
// just console logging as a naive example. there is a lot more you can do here,
// like aggregating stats or filtering out stats you don't want to monitor
console . log (
'Request for resource' ,
stats . resource ,
'with' ,
stats . operation ,
'returned statusCode:' ,
stats . statusCode ,
' within' ,
stats . time ,
'ms' ,
) ;
} ,
} ) ,
) ; Perangkat lunak ini bebas digunakan di bawah Yahoo! Lisensi BSD Inc. Lihat file lisensi untuk teks lisensi dan informasi hak cipta.