第一題:閱讀下面代碼,我們只考慮瀏覽器環(huán)境下的輸出結(jié)果捏萍,寫出它們結(jié)果打印的先后順序罢维,并分析出原因淹仑。
console.log("AAAA");
setTimeout(() => console.log("BBBB"), 1000);
const start = new Date();
while (new Date() - start < 3000) {}
console.log("CCCC");
setTimeout(() => console.log("DDDD"), 0);
new Promise((resolve, reject) => {
console.log("EEEE");
foo.bar(100);
})
.then(() => console.log("FFFF"))
.then(() => console.log("GGGG"))
.catch(() => console.log("HHHH"));
console.log("IIII");
答案解析:
這道題考察重點(diǎn)是 js異步執(zhí)行 宏任務(wù) 微任務(wù)。
一開始代碼執(zhí)行肺孵,輸出AAAA. 1
第二行代碼開啟一個(gè)計(jì)時(shí)器t1(一個(gè)稱呼)攻人,這是一個(gè)異步任務(wù)且是宏任務(wù),需要等到1秒后提交悬槽。
第四行是個(gè)while語句怀吻,需要等待3秒后才能執(zhí)行下面的代碼,這里有個(gè)問題,就是3秒后上一個(gè)計(jì)時(shí)器t1的提交時(shí)間已經(jīng)過了初婆,但是線程上的任務(wù)還沒有執(zhí)行結(jié)束蓬坡,所以暫時(shí)不能打印結(jié)果猿棉,所以它排在宏任務(wù)的最前面了。
第五行又輸出CCCC
第六行又開啟一個(gè)計(jì)時(shí)器t2(稱呼)屑咳,它提交的時(shí)間是0秒(其實(shí)每個(gè)瀏覽器器有默認(rèn)最小時(shí)間的萨赁,暫時(shí)忽略),但是之前的t1任務(wù)還沒有執(zhí)行兆龙,還在等待杖爽,所以t2就排在t1的后面。(t2排在t1后面的原因是while造成的)都還需要等待紫皇,因?yàn)榫€程上的任務(wù)還沒執(zhí)行完畢慰安。
第七行new Promise將執(zhí)行promise函數(shù),它參數(shù)是一個(gè)回調(diào)函數(shù)聪铺,這個(gè)回調(diào)函數(shù)內(nèi)的代碼是同步的化焕,它的異步核心在于resolve和reject,同時(shí)這個(gè)異步任務(wù)在任務(wù)隊(duì)列中屬于微任務(wù)铃剔,是優(yōu)先于宏任務(wù)執(zhí)行的撒桨,(不管宏任務(wù)有多急,反正我是VIP)键兜。所以先直接打印輸出同步代碼EEEE凤类。第九行中的代碼是個(gè)不存在的對象,這個(gè)錯(cuò)誤要拋給reject這個(gè)狀態(tài)普气,也就是catch去處理谜疤,但是它是異步的且是微任務(wù),只有等到線程上的任務(wù)執(zhí)行完畢棋电,立馬執(zhí)行它茎截,不管宏任務(wù)(計(jì)時(shí)器,ajax等)等待多久了赶盔。
第十四行企锌,這是線程上的最后一個(gè)任務(wù),打印輸出 IIII
我們先找出線程上的同步代碼于未,將結(jié)果依次排列出來:AAAA CCCC EEEE IIII
然后我們再找出所有異步任務(wù)中的微任務(wù) 把結(jié)果打印出來 HHHH
最后我們再找出異步中的所有宏任務(wù)撕攒,這里t1排在前面t2排在后面(這個(gè)原因是while造成的),輸出結(jié)果順序是 BBBB DDDD
所以綜上 結(jié)果是 AAAA CCCC EEEE IIII HHHH BBBB DDDD
第二題:閱讀下面代碼烘浦,我們只考慮瀏覽器環(huán)境下的輸出結(jié)果抖坪,寫出它們結(jié)果打印的先后順序,并分析出原因闷叉。
async function async1() {
console.log("AAAA");
async2();
console.log("BBBB");
}
async function async2() {
console.log("CCCC");
}
console.log("DDDD");
setTimeout(function () {
console.log("FFFF");
}, 0);
async1();
new Promise(function (resolve) {
console.log("GGGG");
resolve();
}).then(function () {
console.log("HHHH");
});
console.log("IIII");
答案解析:
這道題考察重點(diǎn)是 js異步執(zhí)行 宏任務(wù) 微任務(wù).
這道題的坑就在于 async中如果沒有await擦俐,那么它就是一個(gè)純同步函數(shù)。
這道題的起始代碼在第9行握侧,輸出DDDD
第10行計(jì)時(shí)器開啟一個(gè)異步任務(wù)t1(一個(gè)稱呼)蚯瞧,這個(gè)任務(wù)且為宏任務(wù)嘿期。
第13行函數(shù)async1執(zhí)行,這個(gè)函數(shù)內(nèi)沒有await 所以它其實(shí)就是一個(gè)純同步函數(shù)埋合,打印輸出AAAA,
在async1中執(zhí)行async2函數(shù)备徐,因?yàn)閍sync2的內(nèi)部也沒有await,所以它也是個(gè)純同步函數(shù)甚颂,打印輸出CCCC
緊接著打印輸出BBBB蜜猾。
第14行new Promise執(zhí)行里面的代碼也是同步的,所以打印輸出GGGG,resolve()調(diào)用的時(shí)候開啟一個(gè)異步任務(wù)t2(一個(gè)稱呼),且這個(gè)任務(wù)t2是微任務(wù)振诬,它的執(zhí)行交給then()中的第一個(gè)回調(diào)函數(shù)執(zhí)行蹭睡,且優(yōu)先級高于宏任務(wù)(t1)執(zhí)行。
第20行打印輸出IIII,此時(shí)線程上的同步任務(wù)全部執(zhí)行結(jié)束贷揽。
在執(zhí)行任務(wù)隊(duì)列中的異步任務(wù)時(shí)棠笑,微任務(wù)優(yōu)先于宏任務(wù)執(zhí)行梦碗,所以先執(zhí)行微任務(wù) t2 打印輸出 HHHH,然后執(zhí)行宏任務(wù) t1 打印輸出 FFFF
所以綜上 結(jié)果輸出是 DDDD AAAA CCCC BBBB GGGG IIII HHHH FFFF
第三題:寫出以下代碼的運(yùn)行結(jié)果禽绪,共有七小問。
問題1
async function t1() {
let a = await "mango";
console.log(a);
}
t1()
答案解析:
await
是一個(gè)表達(dá)式洪规,如果后面不是一個(gè)promise對象印屁,就直接返回對應(yīng)的值。
所以問題1可以理解為
async function t1() {
let a = "mango";
console.log(a);//mango
}
t1()
問題2
async function t2() {
let a = await new Promise((resolve) => {});
console.log(a);//
}
t2()
答案解析:
await
后面如果跟一個(gè)promise對象斩例,await將等待這個(gè)promise對象的resolve狀態(tài)的值value雄人,且將這個(gè)值返回給前面的變量,此時(shí)的promise對象的狀態(tài)是一個(gè)pending狀態(tài)念赶,沒有resolve狀態(tài)值础钠,所以什么也打印不了。
問題3
async function t3() {
let a = await new Promise((resolve) => {
resolve();
});
console.log(a);//undefined
}
t3()
答案解析:
await
后面如果跟一個(gè)promise對象叉谜,await將等待這個(gè)promise對象的resolve狀態(tài)的值value旗吁,且將這個(gè)值返回給前面的變量,此時(shí)的promise對象的狀態(tài)是一個(gè)resolve狀態(tài)停局,但是它的狀態(tài)值是undefined很钓,所以打印出undefined。
問題4
async function t4() {
let a = await new Promise((resolve) => {
resolve("hello");
});
console.log(a);//hello
}
t4()
答案解析:
await
后面如果跟一個(gè)promise對象董栽,await將等待這個(gè)promise對象的resolve狀態(tài)的值码倦,且將這個(gè)值返回給前面的變量,此時(shí)的promise對象的狀態(tài)是一個(gè)resolve狀態(tài)锭碳,它的狀態(tài)值是hello袁稽,所以打印出hello。
問題5
async function t5() {
let a = await new Promise((resolve) => {
resolve("hello");
}).then(() => {
return "lala";
});
console.log(a);//lala
}
t5()
答案解析:
await
后面如果跟一個(gè)promise對象擒抛,await將等待這個(gè)promise對象的resolve狀態(tài)的值推汽,且將這個(gè)值返回給前面的變量蝗柔,此時(shí)的promise對象的狀態(tài)是一個(gè)resolve狀態(tài),它的狀態(tài)值是hello民泵,緊接著后面又執(zhí)行了一個(gè)then方法癣丧,then方法又會返回一個(gè)全新的promise對象,且這個(gè)then方法中的返回值會作為這個(gè)全新的promise中resolve的值栈妆,所以最終的結(jié)果是lala胁编。
問題6
async function t6() {
let a = await fn().then((res)=>{return res})
console.log(a);//undefined
}
async function fn(){
await new Promise((resolve)=>{
resolve("mango")
})
}
t6()
答案解析:
async
函數(shù)執(zhí)行返回一個(gè)promise
對象,且async
函數(shù)內(nèi)部的返回值會當(dāng)作這個(gè)promise對象resolve狀態(tài)的值
async function fn() {
return "la";
}
var p = fn();
console.log(p); //Promise {<resolved>: "la"}
//__proto__: Promise
//[[PromiseStatus]]: "resolved"
//[[PromiseValue]]: "la"
首先考慮 fn()
執(zhí)行返回一個(gè)promise對象,因?yàn)閒n執(zhí)行沒有返回值鳞尔,所以這個(gè)promise對象的狀態(tài)resolve的值是undefined嬉橙,且將這個(gè)undefined當(dāng)作下一個(gè)then中回調(diào)函數(shù)的參數(shù),所以打印的結(jié)果是undefined
問題7
async function t7() {
let a = await fn().then((res)=>{return res})
console.log(a);
}
async function fn(){
await new Promise((resolve)=>{
resolve("mango")
})
return "lala"
}
t7()
答案解析:
首先考慮 fn()
執(zhí)行返回一個(gè)promise對象寥假,因?yàn)?code>fn()執(zhí)行有返回值lala市框,所以這個(gè)promise對象的狀態(tài)resolve的值是lala,且將這個(gè)lala當(dāng)作下一個(gè)then中回調(diào)函數(shù)的參數(shù)糕韧,所以打印的結(jié)果是lala枫振。
注意細(xì)節(jié)
async函數(shù)執(zhí)行的返回結(jié)果是一個(gè)promise對象,這個(gè)函數(shù)的返回值是這個(gè)promise狀態(tài)值resolve的值
await后面如果不是一個(gè)promise對象萤彩,將直接返回這個(gè)值
-
await后面如果是一個(gè)promise對象粪滤,將會把這個(gè)promise的狀態(tài)resolve的值返回出去。
以上沒有考慮reject狀態(tài)雀扶。
第四題:談一談下面兩種寫法的區(qū)別:
//第一種
promise.then((res) => {
console.log('then:', res)
}).catch((err) => {
console.log('catch:', err)
})
//第二種
promise.then((res) => {
console.log('then:', res)
}, (err) => {
console.log('catch:', err)
})
答案解析:
第一種catch方法可以捕獲到catch之前的整條鏈路上拋出的異常杖小。
第二種then方法的第二個(gè)參數(shù)捕獲的異常依賴于上一個(gè)Promise對象的執(zhí)行結(jié)果。
promise.then(seccessCb,fallCb)接收兩個(gè)函數(shù)作為參數(shù)愚墓,來處理上一個(gè)promise對象的結(jié)果予权。then方法返回的是promise對象。
第一種鏈?zhǔn)綄懛ɡ瞬幔褂胏atch扫腺,相當(dāng)于給前一個(gè)then方法返回的promise注冊回調(diào),可以捕獲到前面then沒有被處理的異常议经。
第二種是回調(diào)的寫法斧账,僅為上一個(gè)promise注冊異常回調(diào)煞肾。
如果是promise內(nèi)部報(bào)錯(cuò)reject拋出錯(cuò)誤后咧织,then的第二個(gè)參數(shù)就能捕獲到,如果then的第二個(gè)參數(shù)不存在籍救,則catch方法會捕獲到习绢。
如果是then的第一個(gè)參數(shù)函數(shù)resolve中拋出了異常,即成功回調(diào)函數(shù)出現(xiàn)異常后,then的第二個(gè)參數(shù)reject捕獲不到闪萄,但是catch方法可以捕獲到梧却。
第五題:根據(jù)題目寫出符合要求的代碼
var urls = [
'http://jsonplaceholder.typicode.com/posts/1',
'http://jsonplaceholder.typicode.com/posts/2',
'http://jsonplaceholder.typicode.com/posts/3',
'http://jsonplaceholder.typicode.com/posts/4',
'http://jsonplaceholder.typicode.com/posts/5',
'http://jsonplaceholder.typicode.com/posts/6',
'http://jsonplaceholder.typicode.com/posts/7',
'http://jsonplaceholder.typicode.com/posts/8',
'http://jsonplaceholder.typicode.com/posts/9',
'http://jsonplaceholder.typicode.com/posts/10'
]
function loadDate (url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.onload = function () {
resolve(xhr.responseText)
}
xhr.open('GET', url)
xhr.send()
})
}
在 urls 數(shù)組中存放了 10 個(gè)接口地址。同時(shí)還定義了一個(gè) loadDate 函數(shù)败去,這個(gè)函數(shù)接受一個(gè) url 參數(shù)放航,返回一個(gè) Promise 對象,該 Promise 在接口調(diào)用成功時(shí)返回 resolve圆裕,失敗時(shí)返回 reject广鳍。
要求:任意時(shí)刻,同時(shí)下載的鏈接數(shù)量不可以超過 3 個(gè)吓妆。 試寫出一段代碼實(shí)現(xiàn)這個(gè)需求赊时,要求盡可能快速地將所有接口中的數(shù)據(jù)得到。
答案解析:
按照題意我們可以這樣做行拢,首先并發(fā)請求 3 個(gè) url 中的數(shù)據(jù)祖秒,當(dāng)其中一條 url 請求得到數(shù)據(jù)后,立即發(fā)起對一條新 url 上數(shù)據(jù)的請求舟奠,我們要始終讓并發(fā)數(shù)保持在 3 個(gè)竭缝,直到所有需要加載數(shù)據(jù)的 url 全部都完成請求并得到數(shù)據(jù)。
用 Promise 實(shí)現(xiàn)的思路就是鸭栖,首先并發(fā)請求3個(gè) url 歌馍,得到 3 個(gè) Promise 握巢,然后組成一個(gè)叫 promises 的數(shù)組晕鹊。再不斷的調(diào)用 Promise.race 來返回最快改變狀態(tài)的 Promise ,然后從數(shù)組promises中刪掉這個(gè) Promise 對象暴浦,再加入一個(gè)新的 Promise溅话,直到所有的 url 被取完,最后再使用 Promise.all 來處理一遍數(shù)組promises中沒有改變狀態(tài)的 Promise歌焦。
var urls = [
'http://jsonplaceholder.typicode.com/posts/1',
'http://jsonplaceholder.typicode.com/posts/2',
'http://jsonplaceholder.typicode.com/posts/3',
'http://jsonplaceholder.typicode.com/posts/4',
'http://jsonplaceholder.typicode.com/posts/5',
'http://jsonplaceholder.typicode.com/posts/6',
'http://jsonplaceholder.typicode.com/posts/7',
'http://jsonplaceholder.typicode.com/posts/8',
'http://jsonplaceholder.typicode.com/posts/9',
'http://jsonplaceholder.typicode.com/posts/10'
]
function loadDate (url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.onload = function () {
resolve(xhr.responseText)
}
xhr.open('GET', url)
xhr.send()
})
}
function limitLoad(urls, handler, limit) {
// 對數(shù)組進(jìn)行一個(gè)拷貝
const sequence = [].concat(urls)
let promises = [];
//實(shí)現(xiàn)并發(fā)請求達(dá)到最大值
promises = sequence.splice(0, limit).map((url, index) => {
// 這里返回的 index 是任務(wù)在數(shù)組 promises 的腳標(biāo)
//用于在 Promise.race 后找到完成的任務(wù)腳標(biāo)
return handler(url).then(() => {
return index
});
});
// 利用數(shù)組的 reduce 方法來以隊(duì)列的形式執(zhí)行
return sequence.reduce((last, url, currentIndex) => {
return last.then(() => {
// 返回最快改變狀態(tài)的 Promise
return Promise.race(promises)
}).catch(err => {
// 這里的 catch 不僅用來捕獲前面 then 方法拋出的錯(cuò)誤
// 更重要的是防止中斷整個(gè)鏈?zhǔn)秸{(diào)用
console.error(err)
}).then((res) => {
// 用新的 Promise 替換掉最快改變狀態(tài)的 Promise
promises[res] = handler(sequence[currentIndex]).then(
() => { return res });
})
}, Promise.resolve()).then(() => {
return Promise.all(promises)
})
}
limitLoad(urls, loadDate, 3)
/*
因?yàn)?loadDate 函數(shù)也返回一個(gè) Promise
所以當(dāng) 所有圖片加載完成后可以繼續(xù)鏈?zhǔn)秸{(diào)用
limitLoad(urls, loadDate, 3).then(() => {
console.log('所有url數(shù)據(jù)請求成功');
}).catch(err => {
console.error(err);
})
*/