成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

setTimeout&Promise&Async之間的愛(ài)恨情仇

Half / 636人閱讀

摘要:但是提出標(biāo)準(zhǔn),允許腳本創(chuàng)建多個(gè)線程,但是子線程完全受主線程控制。只是將事件插入了任務(wù)隊(duì)列,必須等到當(dāng)前代碼執(zhí)行棧執(zhí)行完,主線程才會(huì)去執(zhí)行它指定的回調(diào)函數(shù)。之后全局上下文進(jìn)入函數(shù)調(diào)用棧。

setTimeout 一、setTimeout 初現(xiàn)
定義:setTimeout() 方法用于在指定的毫秒數(shù)后調(diào)用函數(shù)或計(jì)算表達(dá)式。

語(yǔ)法:

    setTimeout(code, milliseconds, param1, param2, ...)
    setTimeout(function, milliseconds, param1, param2, ...)
參數(shù) 描述
code/function 必需。要調(diào)用一個(gè)代碼串,也可以是一個(gè)函數(shù)
milliseconds 可選。執(zhí)行或調(diào)用 code/function 需要等待的時(shí)間,以毫秒計(jì)。默認(rèn)為 0。
param1, param2, ... 可選。 傳給執(zhí)行函數(shù)的其他參數(shù)(IE9 及其更早版本不支持該參數(shù))。
二、setTimeout 初識(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  ,沒(méi)有延遲

按照定義:setTimeout() 方法用于在指定的毫秒數(shù)后調(diào)用函數(shù)或計(jì)算表達(dá)式。第一種方法在指定毫秒數(shù)之后執(zhí)行,第二種方法沒(méi)有在指定毫秒數(shù)后執(zhí)行,而是立刻執(zhí)行。所以我個(gè)人將其分成正規(guī)軍setTimeout和雜牌軍setTimeout,方便后面記憶。

正規(guī)軍我們?cè)诤竺嬖敿?xì)講,現(xiàn)在先了解下雜牌軍:

由于setTimeout()的第一個(gè)參數(shù)是**直接可執(zhí)行的代碼**,所以它沒(méi)有任何延遲效果,如下:
setTimeout(console.log(1), 1000);

//輸出結(jié)果如下:
//直接輸出 1 ,沒(méi)有延遲
三、setTimeout 再遇
setTimeout(function(a,b){
    console.log(a+b);
},1000,4,5);

//輸出結(jié)果如下:
//9

從第三個(gè)參數(shù)開(kāi)始,依次用來(lái)表示第一個(gè)參數(shù)(回調(diào)函數(shù))傳入的參數(shù)
一些古老的瀏覽器是不支持,可以用bind或apply方法來(lái)解決,如下:

setTimeout(function(a,b){
    console.log(a+b);
}.bind(this,4,5),1000);

//輸出結(jié)果如下:
//9

第一個(gè)參數(shù)表示將原函數(shù)的this綁定全局作用域,第二個(gè)參數(shù)開(kāi)始是要傳入原函數(shù)的參數(shù)
當(dāng)調(diào)用綁定函數(shù)時(shí),綁定函數(shù)會(huì)以創(chuàng)建它時(shí)傳入bind()方法的第一個(gè)參數(shù)作為 this

四、setTimeout 相知

對(duì)于setTimeout()的this問(wèn)題,網(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é)可能會(huì)疑惑,第一段代碼延遲三秒之后執(zhí)行輸出1,3,2可以理解,但是第二段代碼延遲0秒執(zhí)行為什么也是會(huì)輸出1,3,2呢?

這里就需要提到“任務(wù)隊(duì)列”這個(gè)概念了,由于JavaScript是一種單線程的語(yǔ)言,也就是說(shuō)同一時(shí)間只能做一件事情。但是HTML5提出Web Worker標(biāo)準(zhǔn),允許JavaScript腳本創(chuàng)建多個(gè)線程,但是子線程完全受主線程控制。
單線程意味著,所有的任務(wù)需要排隊(duì),前一個(gè)任務(wù)結(jié)束,才會(huì)執(zhí)行后一個(gè)任務(wù)。如果前一個(gè)任務(wù)耗時(shí)很長(zhǎng),后一個(gè)任務(wù)就不得不一直等待。

所以設(shè)計(jì)者將任務(wù)分成兩種,一種 同步任務(wù) ,另一種是 異步任務(wù) 。
同步任務(wù)是,在主線程上排隊(duì)執(zhí)行的任務(wù),只有前一個(gè)執(zhí)行完,才能執(zhí)行后一個(gè);
異步任務(wù)是,不進(jìn)入主線程,而是進(jìn)入“任務(wù)隊(duì)列”的任務(wù),只有“任務(wù)隊(duì)列”通知主線程,某個(gè)異步任務(wù)可以執(zhí)行了,該任務(wù)才會(huì)進(jìn)入主線程執(zhí)行。

“任務(wù)隊(duì)列”除了放置任務(wù)事件,還可以放置定時(shí)事件。即指定某些代碼在多少時(shí)間之后執(zhí)行。知道了這些我們基本上就可以解釋上面兩段代碼為什么會(huì)輸出這樣的結(jié)果了。

第一段代碼,因?yàn)閟etTimeout()將回調(diào)函數(shù)推遲了3000毫秒之后執(zhí)行。如果將setTimeout()第二個(gè)參數(shù)設(shè)置為0,就表示當(dāng)前代碼執(zhí)行完以后,立刻執(zhí)行(0毫秒間隔)指定的回調(diào)函數(shù)。所以只有在打印出1和3之后,系統(tǒng)才會(huì)執(zhí)行“任務(wù)隊(duì)列”中的回調(diào)函數(shù)。

總之,setTimeout(fn,0)的含義是,指定某個(gè)任務(wù)在主線程最早可得的空閑時(shí)間執(zhí)行,也就是說(shuō),盡可能早得執(zhí)行。它在"任務(wù)隊(duì)列"的尾部添加一個(gè)事件,因此要等到同步任務(wù)和"任務(wù)隊(duì)列"現(xiàn)有的事件都處理完,才會(huì)得到執(zhí)行。強(qiáng)調(diào)一遍:它在"任務(wù)隊(duì)列"的尾部添加一個(gè)事件,記住是尾部,添加到"任務(wù)隊(duì)列"尾部,所以后最后執(zhí)行。

HTML5標(biāo)準(zhǔn)規(guī)定了setTimeout()的第二個(gè)參數(shù)的最小值(最短間隔),不得低于4毫秒,如果低于這個(gè)值,就會(huì)自動(dòng)增加。在此之前,老版本的瀏覽器都將最短間隔設(shè)為10毫秒。

setTimeout()只是將事件插入了"任務(wù)隊(duì)列",必須等到當(dāng)前代碼(執(zhí)行棧)執(zhí)行完,主線程才會(huì)去執(zhí)行它指定的回調(diào)函數(shù)。要是當(dāng)前代碼耗時(shí)很長(zhǎng),有可能要等很久,所以并沒(méi)有辦法保證,回調(diào)函數(shù)一定會(huì)在setTimeout()指定的時(shí)間執(zhí)行。所以他們有時(shí)候不一定會(huì)守時(shí)的。守時(shí)的都是好孩子!

阮一峰老師對(duì)任務(wù)隊(duì)列有詳細(xì)的介紹,詳情戳這里

五、setTimeout 相熟

了解了上面的內(nèi)容,我們得拉出來(lái)溜溜了,直接上測(cè)試題:

    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會(huì)延遲一秒)
1.先執(zhí)行主線程,打印出1;

2.遇到第一個(gè)setTimeout,1秒后執(zhí)行回調(diào)函數(shù),所以添加到任務(wù)隊(duì)列;

3.遇到第二個(gè)setTimeout,0秒后執(zhí)行回調(diào)函數(shù),再次添加到任務(wù)隊(duì)列;

4.遇到第三個(gè)setTimeout,這個(gè)第一個(gè)參數(shù)不是回調(diào)函數(shù),而是一個(gè)直接可執(zhí)行的語(yǔ)句,記得我前面講過(guò)的這個(gè)是個(gè)雜牌軍,它不會(huì)添加到任務(wù)隊(duì)列也不會(huì)延遲執(zhí)行而是直接執(zhí)行,所以打印出3;

5.繼續(xù)執(zhí)行打印出5;

6.第二個(gè)setTimeout,由于是0秒延遲所以主線程任務(wù)結(jié)束立刻執(zhí)行,所以打印出2;

7.最后執(zhí)行第一個(gè)setTimeout,一秒后打印出4.

上面的試題明白之后我們就可以明白下面的代碼了:

    var timeoutId = setTimeout(function (){
        console("hello World");
    },1000);
   
    clearTimeout(timeoutId);
    //輸出結(jié)果:
    //不會(huì)打印出hello World
1先執(zhí)行主線程,遇到setTimeout并且第一個(gè)參數(shù)是回調(diào)函數(shù),添加到任務(wù)隊(duì)列,1秒后執(zhí)行;

2.執(zhí)行clearTimeout,則還未等到代碼執(zhí)行就 取消了定時(shí)器,所以不會(huì)打印出任何內(nèi)容。

下面我們學(xué)習(xí)下promise

promise 一、promise 初現(xiàn)

ES6 將promise寫進(jìn)了語(yǔ)言標(biāo)準(zhǔn),統(tǒng)一了用法,原生提供了Promise對(duì)象。
詳細(xì)介紹戳這里阮一峰老師進(jìn)行了詳細(xì)的說(shuō)明;

這里我簡(jiǎn)單的說(shuō)下,我后面會(huì)使用到的內(nèi)容:
Promise 新建后就會(huì)立即執(zhí)行,然后,then方法接受兩個(gè)回調(diào)函數(shù)作為參數(shù),將在當(dāng)前腳本所有同步任務(wù)執(zhí)行完才會(huì)執(zhí)行。記住這里then之后的回調(diào)函數(shù)才異步執(zhí)行的,所以會(huì)添加到任務(wù)隊(duì)列中。
第一個(gè)回調(diào)函數(shù)是Promise對(duì)象的狀態(tài)變?yōu)閞esolved時(shí)調(diào)用,第二個(gè)回調(diào)函數(shù)是Promise對(duì)象的狀態(tài)變?yōu)閞ejected時(shí)調(diào)用。其中,第二個(gè)函數(shù)是可選的,不一定要提供。

二、promise 初識(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 新建后就會(huì)立即執(zhí)行,所以打印出2,執(zhí)行resolve表明執(zhí)行成功回調(diào);

then的成功執(zhí)行的是回調(diào)函數(shù),所以是異步執(zhí)行,添加到任務(wù)隊(duì)列之中,暫不執(zhí)行;

繼續(xù)執(zhí)行主線程,打印出5;

主線程結(jié)束之后執(zhí)行任務(wù)隊(duì)列中的回調(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

這個(gè)例子同上,只是執(zhí)行的是異步的失敗的回調(diào)函數(shù),所以最后一個(gè)打印出的是4

    console.log(1);
    new Promise((resolve,reject)=>{
        console.log(2);
    }).then( ()=>{
        console.log(3)
    });
    console.log(4);

    //輸出結(jié)果:
    //1 2 4

這個(gè)例子中打印出4之后沒(méi)有打印3,是因?yàn)閜romise中沒(méi)有指定是執(zhí)行成功回調(diào)還是失敗的回調(diào)所以不會(huì)執(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

看到這個(gè)有同學(xué)可能就懵了,怎么回事怎么是1234而不是1243呢,這需要考察同學(xué)們是否細(xì)心呢,看這里then中的直接是可執(zhí)行的語(yǔ)句而不是回調(diào)函數(shù),所以會(huì)出現(xiàn)這種情況,異步任務(wù)必須是回調(diào)函數(shù) 如果不是回調(diào)函數(shù)就是同步的了

1.先執(zhí)行主線程,打印出1;

Promise 新建后就會(huì)立即執(zhí)行,所以打印出2;

then中不是回調(diào)函數(shù)而是直接可執(zhí)行的語(yǔ)句,所以直接執(zhí)行打印出3;

繼續(xù)執(zhí)行主線程,打印出4;

嘻嘻,看了上面的這些例子相信大家已經(jīng)對(duì)promise理解了不少,所以我們繼續(xù)深入看看下面這個(gè)例子,輸出的結(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

大家有沒(méi)有寫對(duì)呢?
這里大家的疑問(wèn)估計(jì)就是resolve()之后的console.log(3);這個(gè)地方咯
這是因?yàn)樯厦娲a中,調(diào)用resolve()以后,后面的console.log(3)還是會(huì)執(zhí)行,并且會(huì)首先打印出來(lái)。因?yàn)榱⒓?resolved 的 Promise 是在本輪事件循環(huán)的末尾執(zhí)行,總是晚于本輪循環(huán)的同步任務(wù)。

所以如果想讓,調(diào)用resolve或reject以后,Promise 的使命完成,后繼操作應(yīng)該放到then方法里面,而不應(yīng)該直接寫在resolve或reject的后面。所以,最好在它們前面加上return語(yǔ)句,這樣就不會(huì)有意外。如下:

    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);是不會(huì)執(zhí)行的。

三、promise&setTimeout

下面我們?cè)趤?lái)看如果promise&setTimeout同時(shí)出現(xiàn)會(huì)發(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

大家是不是有些暈,哈哈哈,別著急這里我們得在拓展一點(diǎn)新概念,方便我們理解:事件循環(huán)、宏任務(wù)和微任務(wù)

JavaScript的一大特點(diǎn)就是單線程,而這個(gè)線程中擁有唯一的一個(gè)事件循環(huán)。

一個(gè)線程中,事件循環(huán)是唯一的,但是任務(wù)隊(duì)列可以擁有多個(gè)。

任務(wù)隊(duì)列又分為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(整體代碼)開(kāi)始第一次循環(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)開(kāi)始,找到其中一個(gè)任務(wù)隊(duì)列執(zhí)行完畢,然后再執(zhí)行所有的微任務(wù)(micro-task),這樣一直循環(huán)下去。

注:本篇使用的宏任務(wù)(macro-task):script(整體代碼), setTimeout, setInterval;微任務(wù)(micro-task): Promise。至于其他的瀏覽器沒(méi)有,引用了node.js的API,如: setImmediate、 process.nextTick等,至于他們的執(zhí)行順序可參考這篇文章

比如上述例子,不同類型的任務(wù)會(huì)分別進(jìn)入到他們所屬類型的任務(wù)隊(duì)列,比如所有setTimeout()的回調(diào)都會(huì)進(jìn)入到setTimeout任務(wù)隊(duì)列,既宏任務(wù)(macro-task);所有then()回調(diào)都會(huì)進(jìn)入到then隊(duì)列,既微任務(wù)(micro-task)。

當(dāng)前的整體代碼我們可以認(rèn)為是宏任務(wù)。事件循環(huán)從當(dāng)前整體代碼開(kāi)始第一次事件循環(huán),然后再執(zhí)行隊(duì)列中所有的微任務(wù),當(dāng)微任務(wù)執(zhí)行完畢之后,事件循環(huán)再找到其中一個(gè)宏任務(wù)隊(duì)列并執(zhí)行其中的所有任務(wù),然后再找到一個(gè)微任務(wù)隊(duì)列并執(zhí)行里面的所有任務(wù),就這樣一直循環(huán)下去。這就是我所理解的事件循環(huán)。

分析上面例子:

1.首先執(zhí)行整體代碼,第一個(gè)打印出來(lái)a

2.執(zhí)行到第一個(gè)setTimeout時(shí),發(fā)現(xiàn)它是宏任務(wù),此時(shí)會(huì)新建一個(gè)setTimeout類型的宏任務(wù)隊(duì)列并派發(fā)當(dāng)前這個(gè)setTimeout的回調(diào)函數(shù)到剛建好的這個(gè)宏任務(wù)隊(duì)列中去

3.再執(zhí)行到new Promise,Promise構(gòu)造函數(shù)中的第一個(gè)參數(shù)在new的時(shí)候會(huì)直接執(zhí)行,因此不會(huì)進(jìn)入任何隊(duì)列,所以第三個(gè)輸出是c

4.執(zhí)行完resolve()之后,繼續(xù)向后執(zhí)行,打印出d

5.上面有說(shuō)到Promise.then是微任務(wù),那么這里會(huì)生成一個(gè)Promise.then類型的微任務(wù)隊(duì)列,這里的then回調(diào)會(huì)被push進(jìn)這個(gè)隊(duì)列中

6.再向后走打印出f

7.第一輪事件循環(huán)的宏任務(wù)執(zhí)行完成(整體代碼看做宏任務(wù))。此時(shí)微任務(wù)隊(duì)列中只有一個(gè)Promise.then類型微任務(wù)隊(duì)列。宏任務(wù)隊(duì)列中也只有一個(gè)setTimeout類型的宏任務(wù)隊(duì)列。

8.下面執(zhí)行第一輪事件循環(huán)的微任務(wù),很明顯,會(huì)打印出e,至此第一輪事件循環(huán)完成

9.開(kāi)始第二輪事件循環(huán):執(zhí)行setTimeout類型隊(duì)列(宏任務(wù)隊(duì)列)中的所有任務(wù),只有一個(gè)任務(wù),所以打印出b

10.第二輪事件的宏任務(wù)結(jié)束,這個(gè)事件循環(huán)結(jié)束。

再來(lái)一個(gè)你中有我我中有你的超級(jí)例子,體驗(yàn)下到處是坑的試題,嘿嘿;-)

    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í)行整體代碼,第一個(gè)打印出來(lái)"a";

2.執(zhí)行到第一個(gè)setTimeout時(shí),發(fā)現(xiàn)它是宏任務(wù),此時(shí)會(huì)新建一個(gè)setTimeout類型的宏任務(wù)隊(duì)列并派發(fā)當(dāng)前這個(gè)setTimeout的回調(diào)函數(shù)到剛建好的這個(gè)宏任務(wù)隊(duì)列中去,并且輪到它執(zhí)行時(shí)要延遲2秒后再執(zhí)行;

3.執(zhí)行到第一個(gè)new Promise,Promise構(gòu)造函數(shù)中的第一個(gè)參數(shù)在new的時(shí)候會(huì)直接執(zhí)行,因此不會(huì)進(jìn)入任何隊(duì)列,所以第二個(gè)輸出是"e",resolve()之后的語(yǔ)句會(huì)繼續(xù)執(zhí)行,所以第三個(gè)輸出的是"f",Promise.then是微任務(wù),那么這里會(huì)生成一個(gè)Promise.then類型的微任務(wù)隊(duì)列,這里的then回調(diào)會(huì)被push進(jìn)這個(gè)隊(duì)列中;

4.再執(zhí)行整體代碼,第四個(gè)打印出來(lái)"h";

5.執(zhí)行到第一個(gè)new Promise,Promise構(gòu)造函數(shù)中的第一個(gè)參數(shù)在new的時(shí)候會(huì)直接執(zhí)行,但是這個(gè)是一個(gè)setTimeout,發(fā)現(xiàn)它是宏任務(wù),派發(fā)它的回調(diào)到上面setTimeout類型的宏任務(wù)隊(duì)列中去。后面Promise.then中是一個(gè)可執(zhí)行的代碼,并不是回調(diào)函數(shù),所以會(huì)直接的執(zhí)行,并不會(huì)添加到微任務(wù)中去,所以第五個(gè)輸出的是:"j";

6.執(zhí)行到第二個(gè)setTimeout時(shí),發(fā)現(xiàn)它是宏任務(wù),派發(fā)它的回調(diào)到上面setTimeout類型的宏任務(wù)隊(duì)列中去,但是會(huì)延遲1s執(zhí)行;

7.執(zhí)行整體代碼,第六個(gè)輸出的是"p";

8.第一輪事件循環(huán)的宏任務(wù)執(zhí)行完成(整體代碼看做宏任務(wù))。此時(shí)微任務(wù)隊(duì)列中只有一個(gè)Promise.then類型微任務(wù)隊(duì)列,它里面有一個(gè)任務(wù);宏任務(wù)隊(duì)列中也只有一個(gè)setTimeout類型的宏任務(wù)隊(duì)列;

9.下面執(zhí)行第一輪事件循環(huán)的微任務(wù),很明顯,第七個(gè)輸出的是:"g"。此時(shí)第一輪事件循環(huán)完成;

10.開(kāi)始第二輪事件循環(huán):執(zhí)行setTimeout類型隊(duì)列(宏任務(wù)隊(duì)列)中的所有任務(wù)。發(fā)現(xiàn)有的有延時(shí)有的沒(méi)有延時(shí),所以先執(zhí)行延時(shí)最短的宏任務(wù);

11.執(zhí)行setTimeout,第八個(gè)輸出的是"i";

12.緊接著執(zhí)行延遲1s的setTimeout,所以延遲一秒之后第九個(gè)輸出的是:"k";

13.之后遇到new Promise,Promise構(gòu)造函數(shù)中的第一個(gè)參數(shù)在new的時(shí)候會(huì)直接執(zhí)行,因此不會(huì)進(jìn)入任何隊(duì)列,所以第十個(gè)輸出是"l",之后是一個(gè)return語(yǔ)句,所以后面的代碼不會(huì)執(zhí)行,"m"不會(huì)被輸出出來(lái);

14.但這里發(fā)現(xiàn)了then,又把它push到上面已經(jīng)被執(zhí)行完的then隊(duì)列中去,這里要注意,因?yàn)槌霈F(xiàn)了微任務(wù)then隊(duì)列,所以這里會(huì)執(zhí)行該隊(duì)列中的所有任務(wù)(此時(shí)只有一個(gè)任務(wù)),所以第十一個(gè)輸出的是"n";

15.再延遲1s執(zhí)行setTimeout,所以延遲二秒之后第十二個(gè)輸出的是:"b";

16.之后遇到new Promise,Promise構(gòu)造函數(shù)中的第一個(gè)參數(shù)在new的時(shí)候會(huì)直接執(zhí)行,因此不會(huì)進(jìn)入任何隊(duì)列,所以第十三個(gè)輸出是"c";

17.但這里又發(fā)現(xiàn)了then,又把它push到上面已經(jīng)被執(zhí)行完的then隊(duì)列中去,這里要注意,因?yàn)槌霈F(xiàn)了微任務(wù)then隊(duì)列,所以這里會(huì)執(zhí)行該隊(duì)列中的所有任務(wù)(此時(shí)只有一個(gè)任務(wù)),所以第十四個(gè)輸出的是"d";

噗,終于完了,不知道大家有沒(méi)有理解呢?
生活就是這樣,你以為度過(guò)了一個(gè)難關(guān)前面就是陽(yáng)光大道,但現(xiàn)實(shí)就是這樣,他會(huì)給你再來(lái)一個(gè)難題,接著看下面的代碼,嘿嘿嘿~~~

    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

是不是有點(diǎn)傻了,怎么又出現(xiàn)了async了,別慌別慌且聽(tīng)我慢慢道來(lái),在說(shuō)之前還得大家了解async,阮一峰老師對(duì)此有詳細(xì)的介紹,詳情戳這里

Async 一、async

async的用法,它作為一個(gè)關(guān)鍵字放到函數(shù)前面,用于表示函數(shù)是一個(gè)異步函數(shù),因?yàn)閍sync就是異步的意思, 異步函數(shù)也就意味著該函數(shù)的執(zhí)行不會(huì)阻塞后面代碼的執(zhí)行。

我們先來(lái)觀察下async的返回值,請(qǐng)看下面的代碼:

    async function testAsync() {
        return "hello async";
    }

    const result = testAsync();
    console.log(result);

    //輸出結(jié)果:
    // Promise { "hello async" }

看到這里我們知道了,saync輸出的是一個(gè)promise對(duì)象

async 函數(shù)(包含函數(shù)語(yǔ)句、函數(shù)表達(dá)式)會(huì)返回一個(gè) Promise 對(duì)象,如果在函數(shù)中 return 一個(gè)直接量,async 會(huì)把這個(gè)直接量通過(guò) Promise.resolve() 封裝成 Promise 對(duì)象。

那我們?cè)囅聸](méi)有返回值會(huì)是怎么樣呢?

    async function testAsync() {
        console.log("hello async");
    }

    const result = testAsync();
    console.log(result);

    //輸出結(jié)果:
    // hello async
    // Promise { undefined }

會(huì)返回一個(gè)為空的promis對(duì)象

二、await

從字面意思上看await就是等待,await 等待的是一個(gè)表達(dá)式,這個(gè)表達(dá)式的返回值可以是一個(gè)promise對(duì)象也可以是其他值。

注意到 await 不僅僅用于等 Promise 對(duì)象,它可以等任意表達(dá)式的結(jié)果,所以,await 后面實(shí)際是可以接普通函數(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 是個(gè)運(yùn)算符,用于組成表達(dá)式,await 表達(dá)式的運(yùn)算結(jié)果取決于它等的東西,如果它等到的不是一個(gè) Promise 對(duì)象,那 await 表達(dá)式的運(yùn)算結(jié)果就是它等到的東西。

內(nèi)容 描述
語(yǔ)法 [return_value] = await expression;
表達(dá)式(expression) 一個(gè) Promise 對(duì)象或者任何要等待的值。
返回值(return_value) 返回 Promise 對(duì)象的處理結(jié)果。如果等待的不是 Promise 對(duì)象,則返回該值本身

但是當(dāng)遇到await會(huì)怎么執(zhí)行呢?

async函數(shù)完全可以看作多個(gè)異步操作,包裝成的一個(gè) Promise 對(duì)象,而await命令就是內(nèi)部then命令的語(yǔ)法糖。
當(dāng)函數(shù)執(zhí)行的時(shí)候,一旦遇到await就會(huì)先返回,等到異步操作完成,再接著執(zhí)行函數(shù)體內(nèi)后面的語(yǔ)句.

即,

當(dāng)遇到async函數(shù)體內(nèi)的 await test();時(shí)候,執(zhí)行test(),然后得到返回值value(可以是promise也可以是其他值),組成await value;,若 value是promise對(duì)象時(shí)候,此時(shí)返回的Promise會(huì)被放入到任務(wù)隊(duì)列中等待,await會(huì)讓出線程,跳出 async函數(shù),繼續(xù)執(zhí)行后續(xù)代碼;若 value是其他值,只是不會(huì)被添加到任務(wù)隊(duì)列而已,await也會(huì)讓出線程,跳出 async函數(shù),繼續(xù)執(zhí)行后續(xù)代碼。

明白了這些,我們分析上面最難的那部分代碼:

1.首先執(zhí)行整體代碼,遇到兩個(gè)saync函數(shù),沒(méi)有調(diào)用所以繼續(xù)向下執(zhí)行,所以第一個(gè)輸出的是:"d";

2.執(zhí)行到第一個(gè)setTimeout時(shí),發(fā)現(xiàn)它是宏任務(wù),此時(shí)會(huì)新建一個(gè)setTimeout類型的宏任務(wù)隊(duì)列并派發(fā)當(dāng)前這個(gè)setTimeout的回調(diào)函數(shù)到剛建好的這個(gè)宏任務(wù)隊(duì)列中去,并且輪到它執(zhí)行時(shí)要立刻執(zhí)行;

3.遇到async1(), async1函數(shù)調(diào)用,執(zhí)行async1函數(shù),第二個(gè)輸出的是:"a";

4.然后執(zhí)行到 await async2(),發(fā)現(xiàn) async2 也是個(gè) async 定義的函數(shù),所以直接執(zhí)行了“console.log("c")”。所以第三個(gè)輸出的是:"c";

5.同時(shí)async2返回了一個(gè)Promise,請(qǐng)注意:此時(shí)返回的Promise會(huì)被放入到任務(wù)隊(duì)列中等待,await會(huì)讓出線程,接下來(lái)就會(huì)跳出 async1函數(shù),繼續(xù)往下執(zhí)行?。。?/p>

6.執(zhí)行到 new Promise,前面說(shuō)過(guò)了promise是立即執(zhí)行的,所以第四個(gè)輸出的是:"x";

7.然后執(zhí)行到 resolve 的時(shí)候,resolve這個(gè)任務(wù)就被放到任務(wù)隊(duì)列中等待,然后跳出Promise繼續(xù)往下執(zhí)行,所以第五個(gè)輸出的是:"z";

8.現(xiàn)在調(diào)用棧空出來(lái)了,事件循環(huán)就會(huì)去任務(wù)隊(duì)列里面取任務(wù)繼續(xù)放到調(diào)用棧里面;

9.取到的第一個(gè)任務(wù),就是前面 async1 放進(jìn)去的Promise,執(zhí)行Promise時(shí)候,遇到resolve或者reject函數(shù),這次會(huì)又被放到任務(wù)隊(duì)列中等待,然后再次跳出 async1函數(shù) 繼續(xù)下一個(gè)任務(wù)!??!

10.接下來(lái)取到的下一個(gè)任務(wù),就是前面 new Promise 放進(jìn)去的 resolve回調(diào),執(zhí)行then,所以第六個(gè)輸出的是:"y";

11.調(diào)用棧再次空出來(lái)了,事件循環(huán)就取到了下一個(gè)任務(wù),async1 函數(shù)中的 async2返回的promise對(duì)象的resolve或者reject函數(shù)執(zhí)行,因?yàn)?async2 并沒(méi)有return任何東西,所以這個(gè)resolve的參數(shù)是undefined;

12.此時(shí) await 定義的這個(gè) Promise 已經(jīng)執(zhí)行完并且返回了結(jié)果,所以可以繼續(xù)往下執(zhí)行 async1函數(shù) 后面的任務(wù)了,那就是“console.log("b")”,所以第七個(gè)輸出的是:"b";

13.調(diào)用棧再次的空了出來(lái)終于執(zhí)行setTimeout的宏任務(wù),所以第八個(gè)輸出的是:"e"

哇(@ο@) 哇~,解決了小伙伴們明白沒(méi)有,希望大家了解了就再也不怕面試這種題目啦!
本想著簡(jiǎn)單的寫下面試題的解決步驟沒(méi)想到一下子寫了這么多,耐心讀到這里的小伙伴都是非常棒的,愿你在技術(shù)的路上越走越遠(yuǎn)!

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/102177.html

相關(guān)文章

  • async &amp; await &amp; promise

    摘要:最近項(xiàng)目中用的比較多,所以特地去了解,模仿一下實(shí)現(xiàn)先來(lái)看看使用的方法通過(guò)是通過(guò)使用生成器配合方法模擬的一個(gè)同步操作,這個(gè)技術(shù)有效的避免了傳統(tǒng)回調(diào)和形成的回調(diào)地獄。 最近項(xiàng)目中 asyn & await 用的比較多,所以特地去了解,模仿一下實(shí)現(xiàn)~ 先來(lái)看看 使用 async & await 的方法 async function d () { try { const a = a...

    Dean 評(píng)論0 收藏0
  • Promise &amp;&amp; async/await理解和用法

    摘要:但是中的這種情況與抽象反應(yīng)器模式如何描述完全不同。在處理一個(gè)階段之后并且在移到下一個(gè)隊(duì)列之前,事件循環(huán)將處理兩個(gè)中間隊(duì)列,直到中間隊(duì)列中沒(méi)有剩余的項(xiàng)目。如果沒(méi)有任務(wù)則循環(huán)退出,每一次隊(duì)列處理都被視為事件循環(huán)的一個(gè)階段。 Promise && async/await的理解和用法 為什么需要promise(承諾)這個(gè)東西 在之前我們處理異步函數(shù)都是用回調(diào)這個(gè)方法,回調(diào)嵌套的時(shí)候會(huì)發(fā)現(xiàn) 閱讀...

    王笑朝 評(píng)論0 收藏0
  • js異步從入門到放棄(實(shí)踐篇) — 常見(jiàn)寫法&amp;面試題解析

    摘要:前文該系列下的前幾篇文章分別對(duì)不同的幾種異步方案原理進(jìn)行解析,本文將介紹一些實(shí)際場(chǎng)景和一些常見(jiàn)的面試題。流程調(diào)度里比較常見(jiàn)的一種錯(cuò)誤是看似串行的寫法,可以感受一下這個(gè)例子判斷以下幾種寫法的輸出結(jié)果辨別輸出順序這類題目一般出現(xiàn)在面試題里。 前文 該系列下的前幾篇文章分別對(duì)不同的幾種異步方案原理進(jìn)行解析,本文將介紹一些實(shí)際場(chǎng)景和一些常見(jiàn)的面試題。(積累不太夠,后面想到再補(bǔ)) 正文 流程調(diào)度...

    Awbeci 評(píng)論0 收藏0
  • String、StringBuilder、StringBuffer愛(ài)恨情仇

    摘要:當(dāng)然大多數(shù)情況下就是我們是在單線程下進(jìn)行的操作,所以大多數(shù)情況下是建議用而不用的,就是速度的原因。 第三階段 JAVA常見(jiàn)對(duì)象的學(xué)習(xí) StringBuffer和StringBuilder類 (一) StringBuffer類的概述 (1) 基本概述 下文以StringBuffer為例 前面我們用字符串做拼接,比較耗時(shí)并且也耗內(nèi)存(每次都會(huì)構(gòu)造一個(gè)新的string對(duì)象),而這種拼接操作又...

    stormjun 評(píng)論0 收藏0
  • 詳解JS前端并發(fā)多個(gè)相同請(qǐng)求控制為只發(fā)一個(gè)請(qǐng)求方式

      描述如下  我們要同時(shí)發(fā)多個(gè)相同的請(qǐng)求,第一個(gè)請(qǐng)求成功后,剩余結(jié)果都不會(huì)發(fā)出,返回結(jié)果是成果?! 〖偃绲谝粋€(gè)反饋失敗,第二個(gè)是成功,后面就不會(huì)發(fā)出,后面都直接反饋成功。第三個(gè)才是成功的話,后面就不會(huì)在發(fā)出,后面都反饋成功。依次如此處理,直至最后一個(gè)?!   〔l(fā): 一個(gè)接口請(qǐng)求還處于pending,短時(shí)間內(nèi)就發(fā)送相同的請(qǐng)求  asyncfunctionfetchData(a){   const...

    3403771864 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<