今天在寫一個(gè)前端頁面的時(shí)候發(fā)現(xiàn),我需要實(shí)現(xiàn)一個(gè)類似 Google 搜索的聯(lián)想功能時(shí),我需要手動(dòng)終止自己發(fā)出去的請(qǐng)求,比如:
在這個(gè)時(shí)候,假如用戶一直輸入,如果不做處理那么就會(huì)導(dǎo)致 1s
內(nèi)很多的請(qǐng)求,這是不合理的。比如我輸入 前端的
, 假如不做處理,瀏覽器就會(huì)發(fā)起三個(gè)請(qǐng)求,分別是針對(duì) 前
, 前端
, 前端的
三個(gè)字的請(qǐng)求,并且每一個(gè)都拿到結(jié)果然后渲染到頁面中,但是其實(shí),我們只需要拿到針對(duì) 前端的
的查詢結(jié)果就行了,這個(gè)時(shí)候我認(rèn)為比較好的做法是 當(dāng)檢測到用戶輸入時(shí),取消針對(duì)上一個(gè)搜索字的請(qǐng)求
,于是,我查閱了相關(guān)的資料,首先因?yàn)槲沂褂玫氖?Fetch ,所以在 MDN 上查到了相關(guān)的資料那就是今天的主角 AbortController ,先看官方的 Demo,
var controller = new AbortController();
var signal = controller.signal;
var downloadBtn = document.querySelector('.download');
var abortBtn = document.querySelector('.abort');
downloadBtn.addEventListener('click', fetchVideo);
abortBtn.addEventListener('click', function() {
controller.abort();
console.log('Download aborted');
});
function fetchVideo() {
...
fetch(url, {signal}).then(function(response) {
...
}).catch(function(e) {
reports.textContent = 'Download error: ' + e.message;
})
}
很明顯,一目了然,能夠很清晰的知道,采用這種方式可以手動(dòng)終止 Fetch
,但是可惜的是前端的新技術(shù)的通病, 瀏覽器支持力度, 從 MDN 上可以看到這個(gè)技術(shù)尚處于正在開發(fā)階段。
幾乎都是比較新的瀏覽器才會(huì)支持。
是不是很可惜,如此好的技術(shù)卻只能看著。但是,我們不一定非要使用官方的規(guī)范,我們可以使用第三方的模塊,比如 Axios
,在這個(gè)項(xiàng)目的 github 主頁,能看到關(guān)于 取消的使用,有興趣可以自己去查看并且使用。
后面是我自己想著只使用 Fetch
但是又能手動(dòng)終止的實(shí)現(xiàn),因?yàn)槟母杏X使用 Fetch
很清爽 ??。
我的思路其實(shí)也借鑒了上面兩種方式的思想,簡單的說就是重新封裝異步的請(qǐng)求,然后再通過 setter 去讓異步請(qǐng)求主動(dòng)的 reject
。
Talk is cheap. Show me the code
:
function wrap (func, ...args) {
const cancel = {};
return () => {
const promiseHandle = new Promise((resolve, reject) => {
Object.defineProperty(cancel, 'signal', {
set () {
reject('Abort');
}
})
func(...args).then(v => resolve(v)).catch(err => reject(err))
});
return Object.assign(promiseHandle, {
cancel () {
cancel.signal = true;
}
});
}
}
寫得很簡陋,主要就是利用了 setter
來調(diào)用封裝的 Promise
主動(dòng)的執(zhí)行 setter
內(nèi)的 reject
函數(shù)。
完整示例:
function wrap (func, ...args) {
const cancel = {};
return () => {
const promiseHandle = new Promise((resolve, reject) => {
Object.defineProperty(cancel, 'signal', {
set () {
reject('Abort');
}
})
func(...args).then(v => resolve(v)).catch(err => reject(err))
});
return Object.assign(promiseHandle, {
cancel () {
cancel.signal = true;
}
});
}
}
var func = wrap((v) => new Promise((res, rej) => {
setTimeout(() => res(v), 5000);
}), 23333)
var a = func()
a.then(v => console.log(v)).catch(err => console.error(err))
a.cancel()
感覺使用 setter
這個(gè)有點(diǎn)不太優(yōu)雅,那么在 node 環(huán)境中,我們可以借助 events
這個(gè)內(nèi)置庫來實(shí)現(xiàn)同樣的效果,而且代碼看起來更優(yōu)雅
const EventEmitter = require('events');
function wrap (func, ...args) {
const emitter = new EventEmitter();
return () => {
const promiseHandle = new Promise((resolve, reject) => {
emitter.on('cancel', () => {
reject(new Error('Reject'));
})
func(...args).then(v => resolve(v)).catch(err => reject(err))
});
return Object.assign(promiseHandle, {
cancel () {
emitter.emit('cancel');
}
});
}
}
var func = wrap((v) => new Promise((res, rej) => {
setTimeout(() => res(v), 5000);
}), 23333)
var a = func()
a.then(v => console.log(v)).catch(err => console.error(err))
a.cancel()
同理,還可以用 Rxjs
的觀察者模式來做,基本原理看到這里應(yīng)該很清晰了吧,就是封裝成一個(gè) Promise
然后再定義一個(gè)函數(shù),可以觸發(fā) reject
, 最后再暴露出一個(gè)函數(shù)用于觸發(fā) Promise
里面的函數(shù)。說得有點(diǎn)繞,其實(shí)原理很簡單。
假如有錯(cuò)誤之處,請(qǐng)指正,謝謝??!