知乎上面看到一個面試題。
某個應用模塊由文本框 input,以及按鈕 A,按鈕 B 組成。點擊按鈕 A,會向地址 urlA 發出一個 ajax 請求,並將返回的字符串填充到 input 中(覆蓋 input 中原有的數據),點擊按鈕 B,會向地址 urlB 發出一個 ajax 請求,並將返回的字符串填充到 input 中(覆蓋 input 中原有的數據)。
當用戶依次點擊按鈕 A、B 的時候,預期的效果是 input 依次被 urlA、urlB 返回的數據填充,但是由於到 urlA 的請求返回比較慢,導致 urlB 返回的數據被 urlA 返回的數據覆蓋了,與用戶預期的順序不一致。
請問如何設計代碼,解決這個問題?
作者:欲三更
鏈接:https://zhuanlan.zhihu.com/p/25259283
來源:知乎
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
網上搜了一下,找到的答案我認為不是特別好。如果這個拓展一下,變成依次點擊按鈕ABCDEFG,或者是BAAB來回點,就不好做了。
知乎下面有人說用Rxjs不到十行代碼,我沒試過。不過這 個畢竟是框架,庫。能夠原生解決,還是能學不少東西的。
我認為這個場景明顯是適合用隊列的。當時在思考中我先用隊列粗暴的實現一個同步阻塞發送ajax的程序。后面我感覺應該再優化升級,異步非阻塞發送ajax,實現這個需求。我把的思路過程寫下來。
我們用setTimeout模擬ajax,通過隊列記錄點擊順序,然后通過promise依次執行ajax。前一個ajax執行玩之后,再執行后一個ajax。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<input type="text"/>
<button id="domA">A</button>
<button id="domB">B</button>
</body>
<script>
const domA = document.querySelector('#domA')
const domB = document.querySelector('#domB')
const input = document.querySelector('input');
//模擬ajax
const ajaxA = () => new Promise((resolve, reject) => {
setTimeout(() => {
console.log('ajaxA end')
input.value = 'ajaxA end';
resolve()
}, 4000)
})
const ajaxB = () => new Promise((resolve, reject) => {
setTimeout(() => {
console.log('ajaxB end')
input.value = 'ajaxB end';
resolve()
}, 1000)
})
</script>
<script>
class Queue {
constructor() {
this.store = []
}
enqueue(ele) {
this.store.push(ele)
}
dequeue() {
return this.store.shift();
}
empty() {
if (this.store.length === 0) return true;
else return false;
}
}
const events = new Queue();
let flag = false;
const run = async () => {
flag = true;
while (!events.empty()) {
await events.dequeue()();
}
flag = false;
}
domA.addEventListener('click', () => {
events.enqueue(ajaxA);
if (!flag) { run() }
})
domB.addEventListener('click', () => {
events.enqueue(ajaxB);
if (!flag) { run() }
})
</script>
</html>
依次點擊ABBA

Okay。解決了。但是讓我想再升級一下問題。面試官說你這是同步阻塞發的ajax。我要不阻塞,點擊了按鈕就發ajax,不要浪費時間去等待ajaxA完成了再發ajaxB。顯示的順序還是按點擊的順序先A后B。
我們改一下程序,我們再點擊了按鈕之后立刻執行ajax(),這里個函數返回的是promise,把這個promise存入隊列。隊列執行過程中,登待promise的狀態為resolve之后立刻就改變input的值。
由於ajaxB都已就緒,所以input的值改變非常迅速,肉眼已經跟不上了。我們在控制台打印出來。可以清晰看到ajax和input改變的順序。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<input type="text"/>
<button id="domA">A</button>
<button id="domB">B</button>
</body>
<script>
const domA = document.querySelector('#domA')
const domB = document.querySelector('#domB')
const input = document.querySelector('input');
//模擬ajax
let value;
const ajaxA = () =>new Promise((resolve, reject) => {
setTimeout(() => {
console.log('ajaxA end')
resolve('ajaxA end')
}, 4000)
})
const ajaxB = () => new Promise((resolve, reject) =>{
setTimeout(() => {
console.log('ajaxB end')
resolve('ajaxB end')
}, 1000)
})
</script>
<script>
class Queue {
constructor() {
this.store = []
}
enqueue(ele) {
this.store.push(ele)
}
dequeue() {
return this.store.shift();
}
empty() {
if (this.store.length === 0) return true;
else return false;
}
}
const events = new Queue();
let flag = false;
const run = async () => {
flag = true;
while (!events.empty()) {
let event = events.dequeue();
input.value=await event;
console.log('input.value',input.value)
}
flag = false;
}
domA.addEventListener('click', () => {
events.enqueue(ajaxA());
if (!flag) { run() }
})
domB.addEventListener('click', () => {
events.enqueue(ajaxB());
if (!flag) { run() }
})
</script>
</html>
依次點擊ABBA

