前文
該系列下的前幾篇文章分別對不同的幾種異步方案原理進行解析,本文將介紹一些實際場景和一些常見的面試題。(積累不太夠,後面想到再補)
正文
流程調度(schedule)
流程調度,最常見的就是繼發和併發(或者說串行和並行)兩種類型,在日常工作裏都很常見。接下來結合實際場景進行說明:
1. 串行執行一系列異步操作,每一步依賴前一步的結果
串行執行的關鍵是,將每一個異步任務放到前一個異步任務的回調函數裏執行。
- 場景:一串連續的動畫,每個動畫必須等待前一個動畫完全執行完,並且如果某個動畫執行失敗,則不繼續執行下一個動畫。
- 代碼:
// 這裏假定一共要執行5個動畫
// getAnimation 函數模擬執行動畫 接收參數i表述動畫編號 返回一個promose
const getAnimation = (i) => new Promise((resolve, reject) => {
setTimeout(()=>{
// 隨機返回true或者false
const isSuccess = Math.random() > 0.5
console.log(`第${i}個動畫執行`)
if(isSuccess){
return resolve(isSuccess)
}
return reject(isSuccess)
},1000)
})
// 1.promise實現 核心就是嵌套代碼
const serialScheduleByPromise = () => {
let p = Promise.resolve(true)
const tasks = []
for(let i=0;i < 5; i++){
p = p.then(isSuccess=>{
if(isSuccess){
return getAnimation(i+1)
}
}).catch((err)=>{
return console.log(`執行失敗`)
})
}
}
serialScheduleByPromise()
// 2.async/await實現
const serialScheduleByAsync = async () => {
try{
for(let i=0;i < 5; i++){
await getAnimation(i+1)
}}catch(e){
console.log(`執行失敗`)
}
}
serialScheduleByAsync()
async/await
的語法雖然沒有單獨解析,但是本質就是前一篇介紹的帶自動執行器的generator
而已,因此不再贅述
可以看到,async的寫法代碼更簡潔,而且邏輯更清晰,可讀性更強。
2. 並行執行所有異步操作,等到所有請求完成後,按照讀取請求的順序輸出結果
場景:併發讀取5個數據(爲了方便 分別編號爲1-5),然後按照實際讀取順序結果
const getDataById = (i) => new Promise((resolve, reject) => {
// 隨機延遲一個時間返回結果,
const delay = Math.floor(Math.random() * Math.floor(3000)) // 延遲時間可能爲 0,1000,2000 毫秒
setTimeout(()=>{
return resolve(i)
}, delay)
})
// 1.promise實現
const concurrentScheduleByPromise = ()=>{
const promises = []
const result = []
for(let i = 0;i < 5;i++){
promises[i] = getDataById(i+1)
promises[i].then(i=>{
result.push(i)
})
}
Promise.all(promises).then(()=>{
result.forEach(id=>{
console.log(id)
})
})
}
concurrentScheduleByPromise()
// async/await實現
const concurrentScheduleByAsync = () => {
for(let i = 0 ;i < 5; i++){
let task = async function (){
console.log(await getDataById(i+1))
}
task()
}
}
concurrentScheduleByAsync()
注意辨析這裏concurrentScheduleByAsync
和serialScheduleByAsync
的區別,關鍵點是同一個async
函數內部的await
纔是按順序執行。
流程調度裏比較常見的一種錯誤是“看似串行”的寫法,可以感受一下這個例子:
const getPromise = (name) =>new Promise(resolve=>{
setTimeout(()=>{
console.log(name)
resolve(name)
},1000)
})
// 判斷以下幾種寫法的輸出結果
Promise.resolve().then(getPromise('1a')).then(getPromise('1b')).then(getPromise('1c'))
Promise.resolve().then(()=>getPromise('2a')).then(()=>getPromise('2b')).then(()=>getPromise('2c'))
Promise.resolve().then(getPromise('3a').then(getPromise('3b').then(getPromise('3c'))))
Promise.resolve().then(()=>getPromise('4a').then(()=>getPromise('4b').then(()=>getPromise('4c'))))
辨別輸出順序
這類題目一般出現在面試題裏。
1. 基礎-區分不同任務類型
console.log(1)
new Promise(resolve => {
console.log(2)
setTimeout(() => {
console.log(10)
}, 10)
resolve()
console.log(3)
}).then(() => {
console.log(5)
})
setTimeout(() => {
console.log(7)
Promise.resolve().then(() => {
console.log(9)
})
console.log(8)
})
Promise.resolve().then(() => {
console.log(6)
})
console.log(4)
// 輸出 1 2 3 4 5 6 7 8 9 10
2. 複雜-加入瀏覽器render
<style>
.outer {
padding: 30px;
background-color: aqua;
}
.inner {
height: 100px;
background-color: brown;
}
</style>
<body>
<div class="outer">outer
<div class="inner">inner</div>
</div>
</body>
<script>
var outer = document.querySelector('.outer');
var inner = document.querySelector('.inner');
// Let's listen for attribute changes on the
// outer element
new MutationObserver(function () {
console.log('mutate');
}).observe(outer, {
attributes: true
});
// Here's a click listener…
function onClick() {
console.log('click');
setTimeout(function () {
console.log('timeout');
}, 0);
Promise.resolve().then(function () {
console.log('promise');
});
outer.setAttribute('data-random', Math.random());
}
// …which we'll attach to both elements
inner.addEventListener('click', onClick);
outer.addEventListener('click', onClick);
// innner.click() 試試直接點擊和js執行click的區別
</script>
這類問題實質上就是辨析異步任務隊列類型,詳細內容和解析可以直接看js異步從入門到放棄(三)- 異步任務隊列(task queues)。
小結
這篇文章主要是給這個系列做個簡單的收尾,單獨純異步的問題難點其實也不多,偷個懶,後面想到了再補上。
如果覺得寫得不好/有錯誤/表述不明確,都歡迎指出
如果有幫助,歡迎點贊和收藏,轉載請徵得同意後著明出處。如果有問題也歡迎私信交流,主頁有郵箱地址
如果覺得作者很辛苦,也歡迎打賞~