Dengan pembangunan bahagian hadapan, perkataan tak segerak menjadi semakin biasa. Katakan kita kini mempunyai tugas tak segerak:
Mulakan beberapa permintaan ke pelayan, dan keputusan setiap permintaan digunakan sebagai parameter untuk permintaan seterusnya.
Mari lihat apa yang perlu kita lakukan:
Panggil balik
Perkara pertama yang terlintas di fikiran dan yang paling biasa digunakan ialah fungsi panggil balik. Mari kita buat enkapsulasi ringkas:
let makeAjaxCall = (url, cb) => { // do some ajax // callback with result } makeAjaxCall('http://url1', (result) => { result = JSON.parse(result) })
Hmm, nampak cantik! Tetapi apabila kami cuba menyusun berbilang tugas, kodnya kelihatan seperti ini:
makeAjaxCall('http://url1', (result) => { result = JSON.parse(result) makeAjaxCall(`http://url2?q=${result.query}`, (result) => { result = JSON.parse(result) makeAjaxCall(`http://url3?q=${result.query}`, (result) => { // ... }) }) })
Ya tuhanku! Biarkan longgokan itu }) pergi ke neraka!
Jadi, kami ingin mencuba menggunakan model acara JavaScript:
1. Pub/Sub
Dalam pemprosesan acara DOM, Pub/Sub ialah mekanisme yang sangat biasa Contohnya, kita perlu menambahkan pemantauan acara pada elemen:
elem.addEventListener(type, (evt) => { // handler })
Jadi bolehkah kita membina model yang serupa untuk mengendalikan tugas tak segerak?
Perkara pertama ialah membina pusat pengedaran dan menambah kaedah on / emit:
let PubSub = { events: {}, on(type, handler) { let events = this.events events[type] = events[type] || [] events[type].push(handler) }, emit(type, ...datas) { let events = this.events if (!events[type]) { return } events[type].forEach((handler) => handler(...datas)) } }
Kemudian kita boleh menggunakannya seperti ini:
const urls = [ 'http://url1', 'http://url2', 'http://url3' ] let makeAjaxCall = (url) => { // do some ajax PubSub.emit('ajaxEnd', result) } let subscribe = (urls) => { let index = 0 PubSub.on('ajaxEnd', (result) => { result = JSON.parse(result) if (urls[++index]) { makeAjaxCall(`${urls[index]}?q=${result.query}`) } }) makeAjaxCall(urls[0]) }
Nampaknya tiada perubahan revolusioner berbanding dengan fungsi panggil balik, tetapi kelebihannya ialah kita boleh meletakkan fungsi permintaan dan pemprosesan dalam modul yang berbeza untuk mengurangkan gandingan.
2. Janji
Perubahan revolusioner sebenar ialah spesifikasi Promise. Dengan Promise, kami boleh menyelesaikan tugas tak segerak seperti ini:
let makeAjaxCall = (url) => { return new Promise((resolve, reject) => { // do some ajax resolve(result) }) } makeAjaxCall('http://url1') .then(JSON.parse) .then((result) => makeAjaxCall(`http://url2?q=${result.query}`)) .then(JSON.parse) .then((result) => makeAjaxCall(`http://url3?q=${result.query}`))
Hebat! Ia ditulis seperti fungsi segerak!
Jangan risau, anak muda. Kami mempunyai lebih baik lagi:
3. Penjana
Satu lagi pembunuh besar ES6 ialah Penjana[2]. Dalam fungsi penjana, kita boleh mengganggu pelaksanaan fungsi melalui pernyataan hasil, dan mengulangi penyataan melalui kaedah seterusnya di luar fungsi Lebih penting lagi, kita boleh menyuntik data ke dalam fungsi melalui kaedah seterusnya untuk mengubah tingkah laku secara dinamik fungsi. Contohnya:
function* gen() { let a = yield 1 let b = yield a * 2 return b } let it = gen() it.next() // output: {value: 1, done: false} it.next(10) // a = 10, output: {value: 20, done: false} it.next(100) // b = 100, output: {value: 100, done: true}
Merangkum fungsi makeAjaxCall kami sebelum ini melalui penjana:
let makeAjaxCall = (url) => { // do some ajax iterator.next(result) } function* requests() { let result = yield makeAjaxCall('http://url1') result = JSON.parse(result) result = yield makeAjaxCall(`http://url2?q=${result.query}`) result = JSON.parse(result) result = yield makeAjaxCall(`http://url3?q=${result.query}`) } let iterator = requests() iterator.next() // get everything start
Oh! Logiknya nampak sangat jelas, tetapi rasanya sangat tidak selesa untuk perlu menyuntik iterator dari luar setiap kali...
Jangan risau, mari kita campurkan Promise dan Generator dan lihat apakah ilmu hitam yang akan dihasilkan:
let makeAjaxCall = (url) => { return new Promise((resolve, reject) => { // do some ajax resolve(result) }) } let runGen = (gen) => { let it = gen() let continuer = (value, err) => { let ret try { ret = err ? it.throw(err) : it.next(value) } catch (e) { return Promise.reject(e) } if (ret.done) { return ret.value } return Promise .resolve(ret.value) .then(continuer) .catch((e) => continuer(null, e)) } return continuer() } function* requests() { let result = yield makeAjaxCall('http://url1') result = JSON.parse(result) result = yield makeAjaxCall(`http://url2?q=${result.query}`) result = JSON.parse(result) result = yield makeAjaxCall(`http://url3?q=${result.query}`) } runGen(requests)
Fungsi runGen kelihatan seperti automaton, sangat hebat!
Sebenarnya, kaedah runGen ini adalah pelaksanaan fungsi async ECMAScript 7:
4. fungsi tak segerak
Dalam ES7, fungsi async ciri yang lebih semula jadi[3] diperkenalkan. Menggunakan fungsi async kita boleh menyelesaikan tugasan seperti ini:
let makeAjaxCall = (url) => { return new Promise((resolve, reject) => { // do some ajax resolve(result) }) } ;(async () => { let result = await makeAjaxCall('http://url1') result = JSON.parse(result) result = await makeAjaxCall(`http://url2?q=${result.query}`) result = JSON.parse(result) result = await makeAjaxCall(`http://url3?q=${result.query}`) })()
Sama seperti apabila kami menggabungkan Janji dan Penjana di atas, kata kunci tunggu juga menerima Janji. Dalam fungsi async, penyataan yang selebihnya akan dilaksanakan hanya selepas penyata menunggu selesai Keseluruhan proses adalah sama seperti kita menggunakan fungsi runGen untuk merangkumkan Penjana.
Di atas ialah beberapa mod pengaturcaraan tak segerak JavaScript yang diringkaskan dalam artikel ini, saya harap ia akan membantu pembelajaran semua orang.