Anda tidak akan dapat menghindari efek samping sepenuhnya, tetapi Anda dapat berusaha untuk menghilangkannya secara maksimal jika memungkinkan.
Misalnya kerangka kerja Express secara inheren sangat penting. Anda menjalankan fungsi seperti res.send()
sepenuhnya untuk efek sampingnya (sebagian besar Anda bahkan tidak peduli dengan nilai pengembaliannya).
Apa yang dapat Anda lakukan (selain menggunakan const
untuk semua deklarasi Anda, menggunakan Immutable.js
struktur data, Ramda
, menulis semua fungsi sebagai const fun = arg => expression;
bukannya const fun = (arg) => { statement; statement; };
dll.) akan membuat sedikit abstraksi tentang cara kerja Express biasanya.
Misalnya Anda dapat membuat fungsi yang mengambil req
sebagai parameter dan mengembalikan objek yang berisi status respons, header, dan aliran untuk disalurkan sebagai badan. Fungsi-fungsi itu bisa menjadi fungsi murni dalam arti bahwa nilai pengembaliannya hanya bergantung pada argumennya (objek permintaan) tetapi Anda masih memerlukan beberapa pembungkus untuk benar-benar mengirim respons menggunakan API Express yang secara inheren imperatif. Ini mungkin tidak sepele tapi bisa dilakukan.
Sebagai contoh, pertimbangkan fungsi ini yang mengambil tubuh sebagai objek untuk dikirim sebagai json:
const wrap = f => (req, res) => {
const { status = 200, headers = {}, body = {} } = f(req);
res.status(status).set(headers).json(body);
};
Ini dapat digunakan untuk membuat penangan rute seperti ini:
app.get('/sum/:x/:y', wrap(req => ({
headers: { 'Foo': 'Bar' },
body: { result: +req.params.x + +req.params.y },
})));
menggunakan fungsi yang mengembalikan satu ekspresi tanpa efek samping.
Contoh lengkap:
const app = require('express')();
const wrap = f => (req, res) => {
const { status = 200, headers = {}, body = {} } = f(req);
res.status(status).set(headers).json(body);
};
app.get('/sum/:x/:y', wrap(req => ({
headers: { 'Foo': 'Bar' },
body: { result: +req.params.x + +req.params.y },
})));
app.listen(4444);
Menguji respons:
$ curl localhost:4444/sum/2/4 -v
* Hostname was NOT found in DNS cache
* Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 4444 (#0)
> GET /sum/2/4 HTTP/1.1
> User-Agent: curl/7.35.0
> Host: localhost:4444
> Accept: */*
>
< HTTP/1.1 200 OK
< X-Powered-By: Express
< Foo: Bar
< Content-Type: application/json; charset=utf-8
< Content-Length: 12
< ETag: W/"c-Up02vIPchuYz06aaEYNjufz5tpQ"
< Date: Wed, 19 Jul 2017 15:14:37 GMT
< Connection: keep-alive
<
* Connection #0 to host localhost left intact
{"result":6}
Tentu saja ini hanya ide dasar. Anda bisa membuat wrap()
function menerima janji untuk nilai pengembalian fungsi untuk operasi async, tetapi kemudian itu bisa dibilang tidak bebas dari efek samping:
const wrap = f => async (req, res) => {
const { status = 200, headers = {}, body = {} } = await f(req);
res.status(status).set(headers).json(body);
};
dan seorang penangan:
const delay = (t, v) => new Promise(resolve => setTimeout(() => resolve(v), t));
app.get('/sum/:x/:y', wrap(req =>
delay(1000, +req.params.x + +req.params.y).then(result => ({
headers: { 'Foo': 'Bar' },
body: { result },
}))));
Saya menggunakan .then()
alih-alih async
/await
di handler itu sendiri agar terlihat lebih fungsional, tetapi dapat ditulis sebagai:
app.get('/sum/:x/:y', wrap(async req => ({
headers: { 'Foo': 'Bar' },
body: { result: await delay(1000, +req.params.x + +req.params.y) },
})));
Itu bisa dibuat lebih universal jika fungsi yang merupakan argumen untuk wrap
akan menjadi generator yang alih-alih hanya menghasilkan janji untuk diselesaikan (seperti yang biasanya dilakukan oleh coroutine berbasis generator) itu akan menghasilkan janji untuk menyelesaikan atau membuang untuk streaming, dengan beberapa pembungkus untuk membedakan keduanya. Ini hanyalah ide dasar tetapi dapat dikembangkan lebih jauh.