setTimeout&Promise&Async之间的爱恨情仇

setTimeout

一、setTimeout 初现

定义:setTimeout() 要领用于在指定的毫秒数后挪用函数或盘算表达式。

语法:

    setTimeout(code, milliseconds, param1, param2, ...)
    setTimeout(function, milliseconds, param1, param2, ...)
参数形貌
code/function必需。要挪用一个代码串,也可所以一个函数
milliseconds可选。实行或挪用 code/function 须要守候的时刻,以毫秒计。默认为 0。
param1, param2, …可选。 传给实行函数的其他参数(IE9 及其更早版本不支持该参数)。

二、setTimeout 初识

第一种


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);
}
//输出结果以下:
// 离别耽误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);
}

//输出结果以下:
//直接输出 1 2 3 4  ,没有耽误

根据定义:setTimeout() 要领用于在指定的毫秒数后挪用函数或盘算表达式。第一种要领在指定毫秒数今后实行,第二种要领没有在指定毫秒数后实行,而是马上实行。所以我个人将其分红正规军setTimeout和杂牌军setTimeout,轻易背面影象。

正规军我们在背面细致讲,如今先相识下杂牌军:

由于setTimeout()的第一个参数是**直接可实行的代码**,所以它没有任何耽误结果,以下:

setTimeout(console.log(1), 1000);

//输出结果以下:
//直接输出 1 ,没有耽误

三、setTimeout 再遇


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

//输出结果以下:
//9

从第三个参数最先,顺次用来示意第一个参数(回调函数)传入的参数
一些陈旧的浏览器是不支持,能够用bind或apply要领来处理,以下:


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

//输出结果以下:
//9

第一个参数示意将原函数的this绑定全局作用域,第二个参数最先是要传入原函数的参数
当挪用绑定函数时,绑定函数会以建立它时传入bind()要领的第一个参数作为 this

四、setTimeout 相知

关于setTimeout()的this问题,网上有许多的文章,我就不班门弄斧了,背面若总结的够到位了就写一篇文章引见下。


console.log(1);
setTimeout(function (){
    console.log(2);
},3000);
console.log(3);
//输出结果以下:
//1 3 2

console.log(1);
setTimeout(function (){
    console.log(2);
},0);
console.log(3);

//输出结果以下:
//1 3 2

这里有些同砚能够会迷惑,第一段代码耽误三秒今后实行输出1,3,2能够邃晓,然则第二段代码耽误0秒实行为何也是会输出1,3,2呢?

这里就须要提到“任务行列”这个概念了,由于JavaScript是一种单线程的言语,也就是说同一时刻只能做一件事变。然则HTML5提出Web Worker规范,许可JavaScript剧本建立多个线程,然则子线程完整受主线程掌握。
单线程意味着,一切的任务须要列队,前一个任务终了,才会实行后一个任务。假如前一个任务耗时很长,后一个任务就不得不一向守候。

所以设计者将任务分红两种,一种 同步任务 ,另一种是 异步任务
同步任务是,在主线程上列队实行的任务,只要前一个实行完,才实行后一个;
异步任务是,不进入主线程,而是进入“任务行列”的任务,只要“任务行列”关照主线程,某个异步任务能够实行了,该任务才会进入主线程实行。

“任务行列”除了安排任务事宜,还能够安排定时事宜。即指定某些代码在若干时刻今后实行。知道了这些我们基本上就能够诠释上面两段代码为何会输出如许的结果了。

第一段代码,由于setTimeout()将回调函数推迟了3000毫秒今后实行。假如将setTimeout()第二个参数设置为0,就示意当前代码实行完今后,马上实行(0毫秒距离)指定的回调函数。所以只要在打印出1和3今后,体系才会实行“任务行列”中的回调函数。

总之,setTimeout(fn,0)的寄义是,指定某个任务在主线程最早可得的余暇时刻实行,也就是说,尽量早得实行。它在”任务行列”的尾部增添一个事宜,因而要比及同步任务和”任务行列”现有的事宜都处理完,才会获得实行。强调一遍:它在”任务行列”的尾部增添一个事宜,记着是尾部,增添到”任务行列”尾部,所今后末了实行。

HTML5规范划定了setTimeout()的第二个参数的最小值(最短距离),不得低于4毫秒,假如低于这个值,就会自动增添。在此之前,老版本的浏览器都将最短距离设为10毫秒。

setTimeout()只是将事宜插入了”任务行列”,必需比及当前代码(实行栈)实行完,主线程才会去实行它指定的回调函数。如果当前代码耗时很长,有能够要等良久,所以并没有方法保证,回调函数肯定会在setTimeout()指定的时刻实行。所以他们有时刻不肯定会守时的。守时的都是好孩子!

阮一峰先生对任务行列有细致的引见,概况戳这里

五、setTimeout 熟悉

相识了上面的内容,我们得拉出来溜溜了,直接上测试题:

    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);
    //输出结果:
    //1 3 5 2 4(4会耽误一秒)

1.先实行主线程,打印出1;

2.碰到第一个setTimeout,1秒后实行回调函数,所以增添到任务行列;

3.碰到第二个setTimeout,0秒后实行回调函数,再次增添到任务行列;

4.碰到第三个setTimeout,这个第一个参数不是回调函数,而是一个直接可实行的语句,记得我前面讲过的这个是个杂牌军,它不会增添到任务行列也不会耽误实行而是直接实行,所以打印出3;

5.继承实行打印出5;

6.第二个setTimeout,由因而0秒耽误所以主线程任务终了马上实行,所以打印出2;

7.末了实行第一个setTimeout,一秒后打印出4.

上面的试题邃晓今后我们就能够邃晓下面的代码了:

    var timeoutId = setTimeout(function (){
        console("hello World");
    },1000);
   
    clearTimeout(timeoutId);
    //输出结果:
    //不会打印出hello World

1先实行主线程,碰到setTimeout而且第一个参数是回调函数,增添到任务行列,1秒后实行;

2.实行clearTimeout,则还未比及代码实行就 取消了定时器,所以不会打印出任何内容。

下面我们进修下promise

promise

一、promise 初现

ES6 将promise写进了言语规范,一致了用法,原生供应了Promise对象。
细致引见戳这里阮一峰先生进行了细致的申明;

这里我简朴的说下,我背面会运用到的内容:
Promise 新建后就会马上实行,然后,then要领接收两个回调函数作为参数,将在当前剧本一切同步任务实行完才会实行。记着这里then今后的回调函数才异步实行的,所以会增添到任务行列中。
第一个回调函数是Promise对象的状况变成resolved时挪用,第二个回调函数是Promise对象的状况变成rejected时挪用。个中,第二个函数是可选的,不肯定要供应。

二、promise 初识

下面我将以代码片断的体式格局,逐步看涌现的种种口试题,加深人人的邃晓


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

    //输出结果:
    //1 2 5 3

1.先实行主线程,打印出1;

  1. Promise 新建后就会马上实行,所以打印出2,实行resolve表明实行胜利回调;
  2. then的胜利实行的是回调函数,所所以异步实行,增添到任务行列当中,暂不实行;
  3. 继承实行主线程,打印出5;
  4. 主线程终了今后实行任务行列中的回调函数打印出3

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

    //输出结果:
    //1 2 5 4

这个例子同上,只是实行的是异步的失利的回调函数,所以末了一个打印出的是4


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

    //输出结果:
    //1 2 4

这个例子中打印出4今后没有打印3,是由于promise中没有指定是实行胜利回调照样失利的回调所以不会实行then的回调函数


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

    //输出结果:
    //1 2 3 4

看到这个有同砚能够就懵了,怎样回事怎样是1234而不是1243呢,这须要考核同砚们是不是仔细呢,看这里then中的直接是可实行的语句而不是回调函数,所以会涌现这类状况,异步任务必需是回调函数 假如不是回调函数就是同步的了

1.先实行主线程,打印出1;

  1. Promise 新建后就会马上实行,所以打印出2;
  2. then中不是回调函数而是直接可实行的语句,所以直接实行打印出3;
  3. 继承实行主线程,打印出4;

嘻嘻,看了上面的这些例子置信人人已对promise邃晓了不少,所以我们继承深切看看下面这个例子,输出的结果是什么呢?


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

    //输出结果:
    //1 2 3 5 4

人人有无写对呢?
这里人人的疑问预计就是resolve()今后的console.log(3);这个处所咯
这是由于上面代码中,挪用resolve()今后,背面的console.log(3)照样会实行,而且会起首打印出来。由于马上 resolved 的 Promise 是在本轮事宜轮回的末端实行,老是晚于本轮轮回的同步任务。

所以假如想让,挪用resolve或reject今后,Promise 的任务完成,后继操纵应当放到then要领内里,而不应当直接写在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);
    //输出结果:
    //1 2 5 4

如许console.log(3);是不会实行的。

三、promise&setTimeout

下面我们在来看假如promise&setTimeout同时涌现会发作什么样的状况呢?以下:


    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');
    //输出结果:
    // a c d f e b

人人是不是是有些晕,哈哈哈,别着急这里我们得在拓展一点新概念,轻易我们邃晓:事宜轮回、宏任务和微任务

JavaScript的一大特征就是单线程,而这个线程中具有唯一的一个事宜轮回。

一个线程中,事宜轮回是唯一的,然则任务行列能够具有多个。

任务行列又分为macro-task(宏任务)与micro-task(微任务),它们又被称为task与jobs。

宏任务(macro-task)也许包括:script(团体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering。

微任务(micro-task)也许包括: process.nextTick, Promise, MutationObserver(html5新特征)

事宜轮回的递次,决议了JavaScript代码的实行递次。

它从script(团体代码)最先第一次轮回。今后全局上下文进入函数挪用栈。

直到挪用栈清空(只剩全局),然后实行一切的微任务(micro-task)。

当一切可实行的微任务(micro-task)实行终了今后。

轮回再次从宏任务(macro-task)最先,找到个中一个任务行列实行终了,然后再实行一切的微任务(micro-task),如许一向轮回下去。

注:本篇运用的宏任务(macro-task):script(团体代码), setTimeout, setInterval;微任务(micro-task): Promise。至于其他的浏览器没有,引用了node.js的API,如: setImmediate、 process.nextTick等,至于他们的实行递次可参考这篇文章

比方上述例子,差别范例的任务会离别进入到他们所属范例的任务行列,比方一切setTimeout()的回调都邑进入到setTimeout任务行列,既宏任务(macro-task);一切then()回调都邑进入到then行列,既微任务(micro-task)。

当前的团体代码我们能够认为是宏任务。事宜轮回从当前团体代码最先第一次事宜轮回,然后再实行行列中一切的微任务,当微任务实行终了今后,事宜轮回再找到个中一个宏任务行列并实行个中的一切任务,然后再找到一个微任务行列并实行内里的一切任务,就如许一向轮回下去。这就是我所邃晓的事宜轮回。

剖析上面例子:

1.起首实行团体代码,第一个打印出来a

2.实行到第一个setTimeout时,发明它是宏任务,此时会新建一个setTimeout范例的宏任务行列并派发当前这个setTimeout的回调函数到刚建好的这个宏任务行列中去

3.再实行到new Promise,Promise组织函数中的第一个参数在new的时刻会直接实行,因而不会进入任何行列,所以第三个输出是c

4.实行完resolve()今后,继承向后实行,打印出d

5.上面有说到Promise.then是微任务,那末这里会天生一个Promise.then范例的微任务行列,这里的then回调会被push进这个行列中

6.再向后走打印出f

7.第一轮事宜轮回的宏任务实行完成(团体代码看做宏任务)。此时微任务行列中只要一个Promise.then范例微任务行列。宏任务行列中也只要一个setTimeout范例的宏任务行列。

8.下面实行第一轮事宜轮回的微任务,很明显,会打印出e,至此第一轮事宜轮回完成

9.最先第二轮事宜轮回:实行setTimeout范例行列(宏任务行列)中的一切任务,只要一个任务,所以打印出b

10.第二轮事宜的宏任务终了,这个事宜轮回终了。

再来一个你中有我我中有你的超等例子,体验下到处是坑的试题,嘿嘿;-)


    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');

    //输出结果:
    //a e f h j p g i
    //耽误1s 输出:k l n 
    //再耽误1s 输出:b c d

1.起首实行团体代码,第一个打印出来”a”;

2.实行到第一个setTimeout时,发明它是宏任务,此时会新建一个setTimeout范例的宏任务行列并派发当前这个setTimeout的回调函数到刚建好的这个宏任务行列中去,而且轮到它实行时要耽误2秒后再实行;

3.实行到第一个new Promise,Promise组织函数中的第一个参数在new的时刻会直接实行,因而不会进入任何行列,所以第二个输出是”e”,resolve()今后的语句会继承实行,所以第三个输出的是”f”,Promise.then是微任务,那末这里会天生一个Promise.then范例的微任务行列,这里的then回调会被push进这个行列中;

4.再实行团体代码,第四个打印出来”h”;

5.实行到第一个new Promise,Promise组织函数中的第一个参数在new的时刻会直接实行,然则这个是一个setTimeout,发明它是宏任务,派发它的回调到上面setTimeout范例的宏任务行列中去。背面Promise.then中是一个可实行的代码,并非回调函数,所以会直接的实行,并不会增添到微任务中去,所以第五个输出的是:”j”;

6.实行到第二个setTimeout时,发明它是宏任务,派发它的回调到上面setTimeout范例的宏任务行列中去,然则会耽误1s实行;

7.实行团体代码,第六个输出的是”p”;

8.第一轮事宜轮回的宏任务实行完成(团体代码看做宏任务)。此时微任务行列中只要一个Promise.then范例微任务行列,它内里有一个任务;宏任务行列中也只要一个setTimeout范例的宏任务行列;

9.下面实行第一轮事宜轮回的微任务,很明显,第七个输出的是:”g”。此时第一轮事宜轮回完成;

10.最先第二轮事宜轮回:实行setTimeout范例行列(宏任务行列)中的一切任务。发明有的有延时有的没有延时,所以先实行延时最短的宏任务;

11.实行setTimeout,第八个输出的是”i”;

12.紧接着实行耽误1s的setTimeout,所以耽误一秒今后第九个输出的是:”k”;

13.今后碰到new Promise,Promise组织函数中的第一个参数在new的时刻会直接实行,因而不会进入任何行列,所以第十个输出是”l”,今后是一个return语句,所以背面的代码不会实行,”m”不会被输出出来;

14.但这里发明了then,又把它push到上面已被实行完的then行列中去,这里要注意,由于涌现了微任务then行列,所以这里会实行该行列中的一切任务(此时只要一个任务),所以第十一个输出的是”n”;

15.再耽误1s实行setTimeout,所以耽误二秒今后第十二个输出的是:”b”;

16.今后碰到new Promise,Promise组织函数中的第一个参数在new的时刻会直接实行,因而不会进入任何行列,所以第十三个输出是”c”;

17.但这里又发明了then,又把它push到上面已被实行完的then行列中去,这里要注意,由于涌现了微任务then行列,所以这里会实行该行列中的一切任务(此时只要一个任务),所以第十四个输出的是”d”;

噗,终究完了,不知道人人有无邃晓呢?
生涯就是如许,你认为度过了一个难关前面就是阳光大道,但现实就是如许,他会给你再来一个困难,接着看下面的代码,嘿嘿嘿~~~


    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');

    //输出结果:
    // d a c x z y b e

是不是是有点傻了,怎样又涌现了async了,别慌别慌且听我逐步道来,在说之前还得人人相识async,阮一峰先生对此有细致的引见,概况戳这里

Async

一、async

async的用法,它作为一个关键字放到函数前面,用于示意函数是一个异步函数,由于async就是异步的意义, 异步函数也就意味着该函数的实行不会壅塞背面代码的实行。

我们先来视察下async的返回值,请看下面的代码:


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

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

    //输出结果:
    // Promise { 'hello async' }

看到这里我们知道了,saync输出的是一个promise对象

async 函数(包括函数语句、函数表达式)会返回一个 Promise 对象,假如在函数中 return 一个直接量,async 会把这个直接量经由过程 Promise.resolve() 封装成 Promise 对象。

那我们试下没有返回值会是怎样呢?


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

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

    //输出结果:
    // hello async
    // Promise { undefined }

会返回一个为空的promis对象

二、await

从字面意义上看await就是守候,await 守候的是一个表达式,这个表达式的返回值可所以一个promise对象也可所以其他值。

注意到 await 不单单议用于等 Promise 对象,它能够等恣意表达式的结果,所以,await 背面现实是能够接一般函数挪用或许直接量的。


    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();

    //输出结果:
    // something hello async

await 是个运算符,用于构成表达式,await 表达式的运算结果取决于它等的东西,假如它比及的不是一个 Promise 对象,那 await 表达式的运算结果就是它比及的东西。

内容形貌
语法[return_value] = await expression;
表达式(expression)一个 Promise 对象或许任何要守候的值。
返回值(return_value)返回 Promise 对象的处理结果。假如守候的不是 Promise 对象,则返回该值自身

然则当碰到await会怎样实行呢?

async函数完整能够看做多个异步操纵,包装成的一个 Promise 对象,而await敕令就是内部then敕令的语法糖。
当函数实行的时刻,一旦碰到await就会先返回,比及异步操纵完成,再接着实行函数体内背面的语句.

即,

当碰到async函数体内的 await test();时刻,实行test(),然后获得返回值value(可所以promise也可所以其他值),构成await value;,若 value是promise对象时刻,此时返回的Promise会被放入到任务行列中守候,await会让出线程,跳出 async函数,继承实行后续代码;若 value是其他值,只是不会被增添到任务行列罢了,await也会让出线程,跳出 async函数,继承实行后续代码。

邃晓了这些,我们剖析上面最难的那部份代码:

1.起首实行团体代码,碰到两个saync函数,没有挪用所以继承向下实行,所以第一个输出的是:”d”;

2.实行到第一个setTimeout时,发明它是宏任务,此时会新建一个setTimeout范例的宏任务行列并派发当前这个setTimeout的回调函数到刚建好的这个宏任务行列中去,而且轮到它实行时要马上实行;

3.碰到async1(), async1函数挪用,实行async1函数,第二个输出的是:”a”;

4.然后实行到 await async2(),发明 async2 也是个 async 定义的函数,所以直接实行了“console.log(‘c’)”。所以第三个输出的是:”c”;

5.同时async2返回了一个Promise,请注意:此时返回的Promise会被放入到任务行列中守候,await会让出线程,接下来就会跳出 async1函数,继承往下实行!!!

6.实行到 new Promise,前面说过了promise是马上实行的,所以第四个输出的是:”x”;

7.然后实行到 resolve 的时刻,resolve这个任务就被放到任务行列中守候,然后跳出Promise继承往下实行,所以第五个输出的是:”z”;

8.如今挪用栈空出来了,事宜轮回就会去任务行列内里取任务继承放到挪用栈内里;

9.取到的第一个任务,就是前面 async1 放进去的Promise,实行Promise时刻,碰到resolve或许reject函数,此次会又被放到任务行列中守候,然后再次跳出 async1函数 继承下一个任务!!!

10.接下来取到的下一个任务,就是前面 new Promise 放进去的 resolve回调,实行then,所以第六个输出的是:”y”;

11.挪用栈再次空出来了,事宜轮回就取到了下一个任务,async1 函数中的 async2返回的promise对象的resolve或许reject函数实行,由于 async2 并没有return任何东西,所以这个resolve的参数是undefined;

12.此时 await 定义的这个 Promise 已实行完而且返回了结果,所以能够继承往下实行 async1函数 背面的任务了,那就是“console.log(‘b’)”,所以第七个输出的是:”b”;

13.挪用栈再次的空了出来终究实行setTimeout的宏任务,所以第八个输出的是:”e”

哇(@ο@) 哇~,处理了小伙伴们邃晓没有,愿望人人相识了就再也不怕口试这类问题啦!
本想着简朴的写下口试题的处理步骤没想到一会儿写了这么多,耐烦读到这里的小伙伴都是异常棒的,愿你在手艺的路上越走越远!

    原文作者:Sando
    原文地址: https://segmentfault.com/a/1190000018316444
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞