摘要:但是提出標(biāo)準(zhǔn),允許腳本創(chuàng)建多個線程,但是子線程完全受主線程控制。只是將事件插入了任務(wù)隊列,必須等到當(dāng)前代碼執(zhí)行棧執(zhí)行完,主線程才會去執(zhí)行它指定的回調(diào)函數(shù)。之后全局上下文進(jìn)入函數(shù)調(diào)用棧。
setTimeout 一、setTimeout 初現(xiàn)
定義:setTimeout() 方法用于在指定的毫秒數(shù)后調(diào)用函數(shù)或計算表達(dá)式。
語法:
setTimeout(code, milliseconds, param1, param2, ...) setTimeout(function, milliseconds, param1, param2, ...)
參數(shù) | 描述 |
---|---|
code/function | 必需。要調(diào)用一個代碼串,也可以是一個函數(shù) |
milliseconds | 可選。執(zhí)行或調(diào)用 code/function 需要等待的時間,以毫秒計。默認(rèn)為 0。 |
param1, param2, ... | 可選。 傳給執(zhí)行函數(shù)的其他參數(shù)(IE9 及其更早版本不支持該參數(shù))。 |
第一種
setTimeout(fn1, 1000); setTimeout(fn2, 2000); setTimeout(function fn3(){console.log(3);}, 3000); setTimeout(function (){console.log(4);}, 4000); function fn1(){ console.log(1); } var fn2 = function(){ console.log(2); } //輸出結(jié)果如下: // 分別延遲1,2,3,4秒之后輸出 1 2 3 4
第二種
setTimeout(fn1(), 1000); setTimeout(fn2(), 2000); setTimeout(function fn3(){console.log(3);}(), 3000); setTimeout(function (){console.log(4);}(), 4000); function fn1(){ console.log(1); } var fn2 = function(){ console.log(2); } //輸出結(jié)果如下: //直接輸出 1 2 3 4 ,沒有延遲
按照定義:setTimeout() 方法用于在指定的毫秒數(shù)后調(diào)用函數(shù)或計算表達(dá)式。第一種方法在指定毫秒數(shù)之后執(zhí)行,第二種方法沒有在指定毫秒數(shù)后執(zhí)行,而是立刻執(zhí)行。所以我個人將其分成正規(guī)軍setTimeout和雜牌軍setTimeout,方便后面記憶。
正規(guī)軍我們在后面詳細(xì)講,現(xiàn)在先了解下雜牌軍:
由于setTimeout()的第一個參數(shù)是**直接可執(zhí)行的代碼**,所以它沒有任何延遲效果,如下:
setTimeout(console.log(1), 1000); //輸出結(jié)果如下: //直接輸出 1 ,沒有延遲三、setTimeout 再遇
setTimeout(function(a,b){ console.log(a+b); },1000,4,5); //輸出結(jié)果如下: //9
從第三個參數(shù)開始,依次用來表示第一個參數(shù)(回調(diào)函數(shù))傳入的參數(shù)
一些古老的瀏覽器是不支持,可以用bind或apply方法來解決,如下:
setTimeout(function(a,b){ console.log(a+b); }.bind(this,4,5),1000); //輸出結(jié)果如下: //9
第一個參數(shù)表示將原函數(shù)的this綁定全局作用域,第二個參數(shù)開始是要傳入原函數(shù)的參數(shù)
當(dāng)調(diào)用綁定函數(shù)時,綁定函數(shù)會以創(chuàng)建它時傳入bind()方法的第一個參數(shù)作為 this
對于setTimeout()的this問題,網(wǎng)上有很多的文章,我就不班門弄斧了,后面若總結(jié)的夠到位了就寫一篇文章介紹下。
console.log(1); setTimeout(function (){ console.log(2); },3000); console.log(3); //輸出結(jié)果如下: //1 3 2
console.log(1); setTimeout(function (){ console.log(2); },0); console.log(3); //輸出結(jié)果如下: //1 3 2
這里有些同學(xué)可能會疑惑,第一段代碼延遲三秒之后執(zhí)行輸出1,3,2可以理解,但是第二段代碼延遲0秒執(zhí)行為什么也是會輸出1,3,2呢?
這里就需要提到“任務(wù)隊列”這個概念了,由于JavaScript是一種單線程的語言,也就是說同一時間只能做一件事情。但是HTML5提出Web Worker標(biāo)準(zhǔn),允許JavaScript腳本創(chuàng)建多個線程,但是子線程完全受主線程控制。
單線程意味著,所有的任務(wù)需要排隊,前一個任務(wù)結(jié)束,才會執(zhí)行后一個任務(wù)。如果前一個任務(wù)耗時很長,后一個任務(wù)就不得不一直等待。
所以設(shè)計者將任務(wù)分成兩種,一種 同步任務(wù) ,另一種是 異步任務(wù) 。
同步任務(wù)是,在主線程上排隊執(zhí)行的任務(wù),只有前一個執(zhí)行完,才能執(zhí)行后一個;
異步任務(wù)是,不進(jìn)入主線程,而是進(jìn)入“任務(wù)隊列”的任務(wù),只有“任務(wù)隊列”通知主線程,某個異步任務(wù)可以執(zhí)行了,該任務(wù)才會進(jìn)入主線程執(zhí)行。
“任務(wù)隊列”除了放置任務(wù)事件,還可以放置定時事件。即指定某些代碼在多少時間之后執(zhí)行。知道了這些我們基本上就可以解釋上面兩段代碼為什么會輸出這樣的結(jié)果了。
第一段代碼,因為setTimeout()將回調(diào)函數(shù)推遲了3000毫秒之后執(zhí)行。如果將setTimeout()第二個參數(shù)設(shè)置為0,就表示當(dāng)前代碼執(zhí)行完以后,立刻執(zhí)行(0毫秒間隔)指定的回調(diào)函數(shù)。所以只有在打印出1和3之后,系統(tǒng)才會執(zhí)行“任務(wù)隊列”中的回調(diào)函數(shù)。
總之,setTimeout(fn,0)的含義是,指定某個任務(wù)在主線程最早可得的空閑時間執(zhí)行,也就是說,盡可能早得執(zhí)行。它在"任務(wù)隊列"的尾部添加一個事件,因此要等到同步任務(wù)和"任務(wù)隊列"現(xiàn)有的事件都處理完,才會得到執(zhí)行。強(qiáng)調(diào)一遍:它在"任務(wù)隊列"的尾部添加一個事件,記住是尾部,添加到"任務(wù)隊列"尾部,所以后最后執(zhí)行。
HTML5標(biāo)準(zhǔn)規(guī)定了setTimeout()的第二個參數(shù)的最小值(最短間隔),不得低于4毫秒,如果低于這個值,就會自動增加。在此之前,老版本的瀏覽器都將最短間隔設(shè)為10毫秒。
setTimeout()只是將事件插入了"任務(wù)隊列",必須等到當(dāng)前代碼(執(zhí)行棧)執(zhí)行完,主線程才會去執(zhí)行它指定的回調(diào)函數(shù)。要是當(dāng)前代碼耗時很長,有可能要等很久,所以并沒有辦法保證,回調(diào)函數(shù)一定會在setTimeout()指定的時間執(zhí)行。所以他們有時候不一定會守時的。守時的都是好孩子!
阮一峰老師對任務(wù)隊列有詳細(xì)的介紹,詳情戳這里
五、setTimeout 相熟了解了上面的內(nèi)容,我們得拉出來溜溜了,直接上測試題:
console.log(1); setTimeout(fn1, 1000); setTimeout(function(){ console.log(2); },0); setTimeout(console.log(3),2000); function fn1(){ console.log(4); } console.log(5); //輸出結(jié)果: //1 3 5 2 4(4會延遲一秒)
1.先執(zhí)行主線程,打印出1;2.遇到第一個setTimeout,1秒后執(zhí)行回調(diào)函數(shù),所以添加到任務(wù)隊列;
3.遇到第二個setTimeout,0秒后執(zhí)行回調(diào)函數(shù),再次添加到任務(wù)隊列;
4.遇到第三個setTimeout,這個第一個參數(shù)不是回調(diào)函數(shù),而是一個直接可執(zhí)行的語句,記得我前面講過的這個是個雜牌軍,它不會添加到任務(wù)隊列也不會延遲執(zhí)行而是直接執(zhí)行,所以打印出3;
5.繼續(xù)執(zhí)行打印出5;
6.第二個setTimeout,由于是0秒延遲所以主線程任務(wù)結(jié)束立刻執(zhí)行,所以打印出2;
7.最后執(zhí)行第一個setTimeout,一秒后打印出4.
上面的試題明白之后我們就可以明白下面的代碼了:
var timeoutId = setTimeout(function (){ console("hello World"); },1000); clearTimeout(timeoutId); //輸出結(jié)果: //不會打印出hello World
1先執(zhí)行主線程,遇到setTimeout并且第一個參數(shù)是回調(diào)函數(shù),添加到任務(wù)隊列,1秒后執(zhí)行;2.執(zhí)行clearTimeout,則還未等到代碼執(zhí)行就 取消了定時器,所以不會打印出任何內(nèi)容。
下面我們學(xué)習(xí)下promise
promise 一、promise 初現(xiàn)ES6 將promise寫進(jìn)了語言標(biāo)準(zhǔn),統(tǒng)一了用法,原生提供了Promise對象。
詳細(xì)介紹戳這里阮一峰老師進(jìn)行了詳細(xì)的說明;
這里我簡單的說下,我后面會使用到的內(nèi)容:
Promise 新建后就會立即執(zhí)行,然后,then方法接受兩個回調(diào)函數(shù)作為參數(shù),將在當(dāng)前腳本所有同步任務(wù)執(zhí)行完才會執(zhí)行。記住這里then之后的回調(diào)函數(shù)才異步執(zhí)行的,所以會添加到任務(wù)隊列中。
第一個回調(diào)函數(shù)是Promise對象的狀態(tài)變?yōu)閞esolved時調(diào)用,第二個回調(diào)函數(shù)是Promise對象的狀態(tài)變?yōu)閞ejected時調(diào)用。其中,第二個函數(shù)是可選的,不一定要提供。
下面我將以代碼片段的方式,逐漸看出現(xiàn)的各種面試題,加深大家的理解
console.log(1); new Promise((resolve,reject)=>{ console.log(2); resolve() }).then( ()=>{ console.log(3) },()=>{ console.log(4); }); console.log(5); //輸出結(jié)果: //1 2 5 3
1.先執(zhí)行主線程,打印出1;
Promise 新建后就會立即執(zhí)行,所以打印出2,執(zhí)行resolve表明執(zhí)行成功回調(diào);
then的成功執(zhí)行的是回調(diào)函數(shù),所以是異步執(zhí)行,添加到任務(wù)隊列之中,暫不執(zhí)行;
繼續(xù)執(zhí)行主線程,打印出5;
主線程結(jié)束之后執(zhí)行任務(wù)隊列中的回調(diào)函數(shù)打印出3
console.log(1); new Promise((resolve,reject)=>{ console.log(2); reject() }).then( ()=>{ console.log(3) },()=>{ console.log(4); }); console.log(5); //輸出結(jié)果: //1 2 5 4
這個例子同上,只是執(zhí)行的是異步的失敗的回調(diào)函數(shù),所以最后一個打印出的是4
console.log(1); new Promise((resolve,reject)=>{ console.log(2); }).then( ()=>{ console.log(3) }); console.log(4); //輸出結(jié)果: //1 2 4
這個例子中打印出4之后沒有打印3,是因為promise中沒有指定是執(zhí)行成功回調(diào)還是失敗的回調(diào)所以不會執(zhí)行then的回調(diào)函數(shù)
console.log(1); new Promise((resolve,reject)=>{ console.log(2); }).then(console.log(3)); console.log(4); //輸出結(jié)果: //1 2 3 4
看到這個有同學(xué)可能就懵了,怎么回事怎么是1234而不是1243呢,這需要考察同學(xué)們是否細(xì)心呢,看這里then中的直接是可執(zhí)行的語句而不是回調(diào)函數(shù),所以會出現(xiàn)這種情況,異步任務(wù)必須是回調(diào)函數(shù) 如果不是回調(diào)函數(shù)就是同步的了
1.先執(zhí)行主線程,打印出1;
Promise 新建后就會立即執(zhí)行,所以打印出2;
then中不是回調(diào)函數(shù)而是直接可執(zhí)行的語句,所以直接執(zhí)行打印出3;
繼續(xù)執(zhí)行主線程,打印出4;
嘻嘻,看了上面的這些例子相信大家已經(jīng)對promise理解了不少,所以我們繼續(xù)深入看看下面這個例子,輸出的結(jié)果是什么呢?
console.log(1); new Promise((resolve,reject)=>{ console.log(2); resolve(); console.log(3); }).then( ()=>{ console.log(4) }); console.log(5); //輸出結(jié)果: //1 2 3 5 4
大家有沒有寫對呢?
這里大家的疑問估計就是resolve()之后的console.log(3);這個地方咯
這是因為上面代碼中,調(diào)用resolve()以后,后面的console.log(3)還是會執(zhí)行,并且會首先打印出來。因為立即 resolved 的 Promise 是在本輪事件循環(huán)的末尾執(zhí)行,總是晚于本輪循環(huán)的同步任務(wù)。
所以如果想讓,調(diào)用resolve或reject以后,Promise 的使命完成,后繼操作應(yīng)該放到then方法里面,而不應(yīng)該直接寫在resolve或reject的后面。所以,最好在它們前面加上return語句,這樣就不會有意外。如下:
console.log(1); new Promise((resolve,reject)=>{ console.log(2); return resolve(); console.log(3); }).then( ()=>{ console.log(4) }); console.log(5); //輸出結(jié)果: //1 2 5 4
這樣console.log(3);是不會執(zhí)行的。
三、promise&setTimeout下面我們在來看如果promise&setTimeout同時出現(xiàn)會發(fā)生什么樣的情況呢?如下:
console.log("a"); setTimeout(function() {console.log("b")}, 0); new Promise((resolve, reject) => { for(let i=0; i<10000000; i++) { if(i==999999) { console.log("c"); resolve(); } } console.log("d"); }).then(() => { console.log("e"); }); console.log("f"); //輸出結(jié)果: // a c d f e b
大家是不是有些暈,哈哈哈,別著急這里我們得在拓展一點新概念,方便我們理解:事件循環(huán)、宏任務(wù)和微任務(wù)
JavaScript的一大特點就是單線程,而這個線程中擁有唯一的一個事件循環(huán)。
一個線程中,事件循環(huán)是唯一的,但是任務(wù)隊列可以擁有多個。
任務(wù)隊列又分為macro-task(宏任務(wù))與micro-task(微任務(wù)),它們又被稱為task與jobs。
宏任務(wù)(macro-task)大概包括:script(整體代碼), setTimeout, setInterval, setImmediate, I/O, UI rendering。
微任務(wù)(micro-task)大概包括: process.nextTick, Promise, MutationObserver(html5新特性)
事件循環(huán)的順序,決定了JavaScript代碼的執(zhí)行順序。
它從script(整體代碼)開始第一次循環(huán)。之后全局上下文進(jìn)入函數(shù)調(diào)用棧。
直到調(diào)用棧清空(只剩全局),然后執(zhí)行所有的微任務(wù)(micro-task)。
當(dāng)所有可執(zhí)行的微任務(wù)(micro-task)執(zhí)行完畢之后。
循環(huán)再次從宏任務(wù)(macro-task)開始,找到其中一個任務(wù)隊列執(zhí)行完畢,然后再執(zhí)行所有的微任務(wù)(micro-task),這樣一直循環(huán)下去。
注:本篇使用的宏任務(wù)(macro-task):script(整體代碼), setTimeout, setInterval;微任務(wù)(micro-task): Promise。至于其他的瀏覽器沒有,引用了node.js的API,如: setImmediate、 process.nextTick等,至于他們的執(zhí)行順序可參考這篇文章
比如上述例子,不同類型的任務(wù)會分別進(jìn)入到他們所屬類型的任務(wù)隊列,比如所有setTimeout()的回調(diào)都會進(jìn)入到setTimeout任務(wù)隊列,既宏任務(wù)(macro-task);所有then()回調(diào)都會進(jìn)入到then隊列,既微任務(wù)(micro-task)。
當(dāng)前的整體代碼我們可以認(rèn)為是宏任務(wù)。事件循環(huán)從當(dāng)前整體代碼開始第一次事件循環(huán),然后再執(zhí)行隊列中所有的微任務(wù),當(dāng)微任務(wù)執(zhí)行完畢之后,事件循環(huán)再找到其中一個宏任務(wù)隊列并執(zhí)行其中的所有任務(wù),然后再找到一個微任務(wù)隊列并執(zhí)行里面的所有任務(wù),就這樣一直循環(huán)下去。這就是我所理解的事件循環(huán)。
分析上面例子:
1.首先執(zhí)行整體代碼,第一個打印出來a2.執(zhí)行到第一個setTimeout時,發(fā)現(xiàn)它是宏任務(wù),此時會新建一個setTimeout類型的宏任務(wù)隊列并派發(fā)當(dāng)前這個setTimeout的回調(diào)函數(shù)到剛建好的這個宏任務(wù)隊列中去
3.再執(zhí)行到new Promise,Promise構(gòu)造函數(shù)中的第一個參數(shù)在new的時候會直接執(zhí)行,因此不會進(jìn)入任何隊列,所以第三個輸出是c
4.執(zhí)行完resolve()之后,繼續(xù)向后執(zhí)行,打印出d
5.上面有說到Promise.then是微任務(wù),那么這里會生成一個Promise.then類型的微任務(wù)隊列,這里的then回調(diào)會被push進(jìn)這個隊列中
6.再向后走打印出f
7.第一輪事件循環(huán)的宏任務(wù)執(zhí)行完成(整體代碼看做宏任務(wù))。此時微任務(wù)隊列中只有一個Promise.then類型微任務(wù)隊列。宏任務(wù)隊列中也只有一個setTimeout類型的宏任務(wù)隊列。
8.下面執(zhí)行第一輪事件循環(huán)的微任務(wù),很明顯,會打印出e,至此第一輪事件循環(huán)完成
9.開始第二輪事件循環(huán):執(zhí)行setTimeout類型隊列(宏任務(wù)隊列)中的所有任務(wù),只有一個任務(wù),所以打印出b
10.第二輪事件的宏任務(wù)結(jié)束,這個事件循環(huán)結(jié)束。
再來一個你中有我我中有你的超級例子,體驗下到處是坑的試題,嘿嘿;-)
console.log("a"); setTimeout(function () { console.log("b") new Promise(resolve=> { console.log("c") resolve() }).then(()=> { console.log("d") }) },2000); new Promise((resolve,reject)=>{ console.log("e"); resolve(); console.log("f"); }).then(()=>{ console.log("g") }); console.log("h"); new Promise((resolve,reject)=>{ setTimeout(function () { console.log("i"); },0); }).then(console.log("j")); setTimeout(function () { console.log("k") new Promise(resolve=>{ console.log("l") return resolve() console.log("m") }).then(()=>{ console.log("n") }) },1000); console.log("p"); //輸出結(jié)果: //a e f h j p g i //延遲1s 輸出:k l n //再延遲1s 輸出:b c d
1.首先執(zhí)行整體代碼,第一個打印出來"a";2.執(zhí)行到第一個setTimeout時,發(fā)現(xiàn)它是宏任務(wù),此時會新建一個setTimeout類型的宏任務(wù)隊列并派發(fā)當(dāng)前這個setTimeout的回調(diào)函數(shù)到剛建好的這個宏任務(wù)隊列中去,并且輪到它執(zhí)行時要延遲2秒后再執(zhí)行;
3.執(zhí)行到第一個new Promise,Promise構(gòu)造函數(shù)中的第一個參數(shù)在new的時候會直接執(zhí)行,因此不會進(jìn)入任何隊列,所以第二個輸出是"e",resolve()之后的語句會繼續(xù)執(zhí)行,所以第三個輸出的是"f",Promise.then是微任務(wù),那么這里會生成一個Promise.then類型的微任務(wù)隊列,這里的then回調(diào)會被push進(jìn)這個隊列中;
4.再執(zhí)行整體代碼,第四個打印出來"h";
5.執(zhí)行到第一個new Promise,Promise構(gòu)造函數(shù)中的第一個參數(shù)在new的時候會直接執(zhí)行,但是這個是一個setTimeout,發(fā)現(xiàn)它是宏任務(wù),派發(fā)它的回調(diào)到上面setTimeout類型的宏任務(wù)隊列中去。后面Promise.then中是一個可執(zhí)行的代碼,并不是回調(diào)函數(shù),所以會直接的執(zhí)行,并不會添加到微任務(wù)中去,所以第五個輸出的是:"j";
6.執(zhí)行到第二個setTimeout時,發(fā)現(xiàn)它是宏任務(wù),派發(fā)它的回調(diào)到上面setTimeout類型的宏任務(wù)隊列中去,但是會延遲1s執(zhí)行;
7.執(zhí)行整體代碼,第六個輸出的是"p";
8.第一輪事件循環(huán)的宏任務(wù)執(zhí)行完成(整體代碼看做宏任務(wù))。此時微任務(wù)隊列中只有一個Promise.then類型微任務(wù)隊列,它里面有一個任務(wù);宏任務(wù)隊列中也只有一個setTimeout類型的宏任務(wù)隊列;
9.下面執(zhí)行第一輪事件循環(huán)的微任務(wù),很明顯,第七個輸出的是:"g"。此時第一輪事件循環(huán)完成;
10.開始第二輪事件循環(huán):執(zhí)行setTimeout類型隊列(宏任務(wù)隊列)中的所有任務(wù)。發(fā)現(xiàn)有的有延時有的沒有延時,所以先執(zhí)行延時最短的宏任務(wù);
11.執(zhí)行setTimeout,第八個輸出的是"i";
12.緊接著執(zhí)行延遲1s的setTimeout,所以延遲一秒之后第九個輸出的是:"k";
13.之后遇到new Promise,Promise構(gòu)造函數(shù)中的第一個參數(shù)在new的時候會直接執(zhí)行,因此不會進(jìn)入任何隊列,所以第十個輸出是"l",之后是一個return語句,所以后面的代碼不會執(zhí)行,"m"不會被輸出出來;
14.但這里發(fā)現(xiàn)了then,又把它push到上面已經(jīng)被執(zhí)行完的then隊列中去,這里要注意,因為出現(xiàn)了微任務(wù)then隊列,所以這里會執(zhí)行該隊列中的所有任務(wù)(此時只有一個任務(wù)),所以第十一個輸出的是"n";
15.再延遲1s執(zhí)行setTimeout,所以延遲二秒之后第十二個輸出的是:"b";
16.之后遇到new Promise,Promise構(gòu)造函數(shù)中的第一個參數(shù)在new的時候會直接執(zhí)行,因此不會進(jìn)入任何隊列,所以第十三個輸出是"c";
17.但這里又發(fā)現(xiàn)了then,又把它push到上面已經(jīng)被執(zhí)行完的then隊列中去,這里要注意,因為出現(xiàn)了微任務(wù)then隊列,所以這里會執(zhí)行該隊列中的所有任務(wù)(此時只有一個任務(wù)),所以第十四個輸出的是"d";
噗,終于完了,不知道大家有沒有理解呢?
生活就是這樣,你以為度過了一個難關(guān)前面就是陽光大道,但現(xiàn)實就是這樣,他會給你再來一個難題,接著看下面的代碼,嘿嘿嘿~~~
async function async1() { console.log("a"); await async2(); console.log("b"); } async function async2() { console.log( "c"); } console.log("d"); setTimeout(function () { console.log("e"); },0); async1(); new Promise(function (resolve) { console.log("x"); resolve(); }).then(function () { console.log("y"); }); console.log("z"); //輸出結(jié)果: // d a c x z y b e
是不是有點傻了,怎么又出現(xiàn)了async了,別慌別慌且聽我慢慢道來,在說之前還得大家了解async,阮一峰老師對此有詳細(xì)的介紹,詳情戳這里
Async 一、asyncasync的用法,它作為一個關(guān)鍵字放到函數(shù)前面,用于表示函數(shù)是一個異步函數(shù),因為async就是異步的意思, 異步函數(shù)也就意味著該函數(shù)的執(zhí)行不會阻塞后面代碼的執(zhí)行。
我們先來觀察下async的返回值,請看下面的代碼:
async function testAsync() { return "hello async"; } const result = testAsync(); console.log(result); //輸出結(jié)果: // Promise { "hello async" }
看到這里我們知道了,saync輸出的是一個promise對象
async 函數(shù)(包含函數(shù)語句、函數(shù)表達(dá)式)會返回一個 Promise 對象,如果在函數(shù)中 return 一個直接量,async 會把這個直接量通過 Promise.resolve() 封裝成 Promise 對象。
那我們試下沒有返回值會是怎么樣呢?
async function testAsync() { console.log("hello async"); } const result = testAsync(); console.log(result); //輸出結(jié)果: // hello async // Promise { undefined }
會返回一個為空的promis對象
二、await從字面意思上看await就是等待,await 等待的是一個表達(dá)式,這個表達(dá)式的返回值可以是一個promise對象也可以是其他值。
注意到 await 不僅僅用于等 Promise 對象,它可以等任意表達(dá)式的結(jié)果,所以,await 后面實際是可以接普通函數(shù)調(diào)用或者直接量的。
function getSomething() { return "something"; } async function testAsync() { return Promise.resolve("hello async"); } async function test() { const v1 = await getSomething(); const v2 = await testAsync(); console.log(v1, v2); } test(); //輸出結(jié)果: // something hello async
await 是個運算符,用于組成表達(dá)式,await 表達(dá)式的運算結(jié)果取決于它等的東西,如果它等到的不是一個 Promise 對象,那 await 表達(dá)式的運算結(jié)果就是它等到的東西。
內(nèi)容 | 描述 |
---|---|
語法 | [return_value] = await expression; |
表達(dá)式(expression) | 一個 Promise 對象或者任何要等待的值。 |
返回值(return_value) | 返回 Promise 對象的處理結(jié)果。如果等待的不是 Promise 對象,則返回該值本身 |
但是當(dāng)遇到await會怎么執(zhí)行呢?
async函數(shù)完全可以看作多個異步操作,包裝成的一個 Promise 對象,而await命令就是內(nèi)部then命令的語法糖。
當(dāng)函數(shù)執(zhí)行的時候,一旦遇到await就會先返回,等到異步操作完成,再接著執(zhí)行函數(shù)體內(nèi)后面的語句.
即,
當(dāng)遇到async函數(shù)體內(nèi)的 await test();時候,執(zhí)行test(),然后得到返回值value(可以是promise也可以是其他值),組成await value;,若 value是promise對象時候,此時返回的Promise會被放入到任務(wù)隊列中等待,await會讓出線程,跳出 async函數(shù),繼續(xù)執(zhí)行后續(xù)代碼;若 value是其他值,只是不會被添加到任務(wù)隊列而已,await也會讓出線程,跳出 async函數(shù),繼續(xù)執(zhí)行后續(xù)代碼。
明白了這些,我們分析上面最難的那部分代碼:
1.首先執(zhí)行整體代碼,遇到兩個saync函數(shù),沒有調(diào)用所以繼續(xù)向下執(zhí)行,所以第一個輸出的是:"d";2.執(zhí)行到第一個setTimeout時,發(fā)現(xiàn)它是宏任務(wù),此時會新建一個setTimeout類型的宏任務(wù)隊列并派發(fā)當(dāng)前這個setTimeout的回調(diào)函數(shù)到剛建好的這個宏任務(wù)隊列中去,并且輪到它執(zhí)行時要立刻執(zhí)行;
3.遇到async1(), async1函數(shù)調(diào)用,執(zhí)行async1函數(shù),第二個輸出的是:"a";
4.然后執(zhí)行到 await async2(),發(fā)現(xiàn) async2 也是個 async 定義的函數(shù),所以直接執(zhí)行了“console.log("c")”。所以第三個輸出的是:"c";
5.同時async2返回了一個Promise,請注意:此時返回的Promise會被放入到任務(wù)隊列中等待,await會讓出線程,接下來就會跳出 async1函數(shù),繼續(xù)往下執(zhí)行!!!
6.執(zhí)行到 new Promise,前面說過了promise是立即執(zhí)行的,所以第四個輸出的是:"x";
7.然后執(zhí)行到 resolve 的時候,resolve這個任務(wù)就被放到任務(wù)隊列中等待,然后跳出Promise繼續(xù)往下執(zhí)行,所以第五個輸出的是:"z";
8.現(xiàn)在調(diào)用棧空出來了,事件循環(huán)就會去任務(wù)隊列里面取任務(wù)繼續(xù)放到調(diào)用棧里面;
9.取到的第一個任務(wù),就是前面 async1 放進(jìn)去的Promise,執(zhí)行Promise時候,遇到resolve或者reject函數(shù),這次會又被放到任務(wù)隊列中等待,然后再次跳出 async1函數(shù) 繼續(xù)下一個任務(wù)!!!
10.接下來取到的下一個任務(wù),就是前面 new Promise 放進(jìn)去的 resolve回調(diào),執(zhí)行then,所以第六個輸出的是:"y";
11.調(diào)用棧再次空出來了,事件循環(huán)就取到了下一個任務(wù),async1 函數(shù)中的 async2返回的promise對象的resolve或者reject函數(shù)執(zhí)行,因為 async2 并沒有return任何東西,所以這個resolve的參數(shù)是undefined;
12.此時 await 定義的這個 Promise 已經(jīng)執(zhí)行完并且返回了結(jié)果,所以可以繼續(xù)往下執(zhí)行 async1函數(shù) 后面的任務(wù)了,那就是“console.log("b")”,所以第七個輸出的是:"b";
13.調(diào)用棧再次的空了出來終于執(zhí)行setTimeout的宏任務(wù),所以第八個輸出的是:"e"
哇(@ο@) 哇~,解決了小伙伴們明白沒有,希望大家了解了就再也不怕面試這種題目啦!
本想著簡單的寫下面試題的解決步驟沒想到一下子寫了這么多,耐心讀到這里的小伙伴都是非常棒的,愿你在技術(shù)的路上越走越遠(yuǎn)!
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/102177.html
摘要:最近項目中用的比較多,所以特地去了解,模仿一下實現(xiàn)先來看看使用的方法通過是通過使用生成器配合方法模擬的一個同步操作,這個技術(shù)有效的避免了傳統(tǒng)回調(diào)和形成的回調(diào)地獄。 最近項目中 asyn & await 用的比較多,所以特地去了解,模仿一下實現(xiàn)~ 先來看看 使用 async & await 的方法 async function d () { try { const a = a...
摘要:但是中的這種情況與抽象反應(yīng)器模式如何描述完全不同。在處理一個階段之后并且在移到下一個隊列之前,事件循環(huán)將處理兩個中間隊列,直到中間隊列中沒有剩余的項目。如果沒有任務(wù)則循環(huán)退出,每一次隊列處理都被視為事件循環(huán)的一個階段。 Promise && async/await的理解和用法 為什么需要promise(承諾)這個東西 在之前我們處理異步函數(shù)都是用回調(diào)這個方法,回調(diào)嵌套的時候會發(fā)現(xiàn) 閱讀...
摘要:前文該系列下的前幾篇文章分別對不同的幾種異步方案原理進(jìn)行解析,本文將介紹一些實際場景和一些常見的面試題。流程調(diào)度里比較常見的一種錯誤是看似串行的寫法,可以感受一下這個例子判斷以下幾種寫法的輸出結(jié)果辨別輸出順序這類題目一般出現(xiàn)在面試題里。 前文 該系列下的前幾篇文章分別對不同的幾種異步方案原理進(jìn)行解析,本文將介紹一些實際場景和一些常見的面試題。(積累不太夠,后面想到再補) 正文 流程調(diào)度...
摘要:當(dāng)然大多數(shù)情況下就是我們是在單線程下進(jìn)行的操作,所以大多數(shù)情況下是建議用而不用的,就是速度的原因。 第三階段 JAVA常見對象的學(xué)習(xí) StringBuffer和StringBuilder類 (一) StringBuffer類的概述 (1) 基本概述 下文以StringBuffer為例 前面我們用字符串做拼接,比較耗時并且也耗內(nèi)存(每次都會構(gòu)造一個新的string對象),而這種拼接操作又...
描述如下 我們要同時發(fā)多個相同的請求,第一個請求成功后,剩余結(jié)果都不會發(fā)出,返回結(jié)果是成果。 假如第一個反饋失敗,第二個是成功,后面就不會發(fā)出,后面都直接反饋成功。第三個才是成功的話,后面就不會在發(fā)出,后面都反饋成功。依次如此處理,直至最后一個。 并發(fā): 一個接口請求還處于pending,短時間內(nèi)就發(fā)送相同的請求 asyncfunctionfetchData(a){ const...
閱讀 1538·2021-11-04 16:10
閱讀 2774·2021-09-30 09:48
閱讀 2839·2019-08-29 11:31
閱讀 1578·2019-08-28 18:22
閱讀 3225·2019-08-26 13:44
閱讀 1319·2019-08-26 13:42
閱讀 2845·2019-08-26 10:20
閱讀 754·2019-08-23 17:00