从JavaScript的事件循环到Promise

JS线程是单线程运行机制,就是本身按顺序作本身的事,浏览器线程用于交互和控制,JS能够操做DOM元素,javascript

提及JS中的异步时,咱们须要注意的是,JS中其实有两种异步,一种是基于浏览器的异步IO,好比Ajax,另一种是基于计时方法setTimeout和setInterval的异步。java

对于异步IO,好比ajax,写代码的时候都是顺序执行的,可是在真正处理请求的时候,有一个单独的浏览器线程来处理,而且在处理完成后会触发回调。这种状况下,参与异步过程的其实有2个线程主体,一个是javascript的主线程,另外一个是浏览器线程。es6

熟悉Javascript的人都知道,Javascript内部有一个事件队列,全部的处理方法都在这个队列中,Javascript主线程就是轮询这个队列处理,这个好处是使得CPU永远是忙碌状态。这个机制和Windows中的消息泵是同样的,都是靠消息(事件)驱动,ajax

对于setTimeout和setInterval来讲,当js线程执行到该代码片断时,js主线程会根据所设定的时间,当设定的时间到期时,将设置的回调函数放到事件队列中,而后js主线程继续去执行下边的代码,当js线程执行完主线程上的代码以后,会去循环执行事件队列中的函数。至于setTimeout或者setInterval设定的执行时间在实际表现时会有一些误差,广泛的一个解释为,当定时任务的时间到期时,本应该去执行该回调函数,可是这时js主线程可能还有任务在执行,或者是该回调函数再事件队列中排的比较靠后,就致使该回调函数执行时间与定时器设定时间不一致。编程

那么问题来了,什么是事件队列?数组

eventloop是一个用做队列的数组,eventloop是一个一直在循环执行的,循环的每一轮成为一个tick,在每个tick中,若是队列中有等待事件,那么就会从队列中摘取下一个事件进行执行,这些事件就是咱们以前的回调函数。如今ES6精确指定了事件循环的工做细节,这意味着在技术上将其归入了JavaScript引擎的势力范围,而不仅是由宿主环境决定了,主要的一个缘由是ES6中promise的引入。promise

var eventloop = []
var event;
while(true){
    if(eventloop.length>0){
        //拿到队列中的下一个事件
        event = eventloop.shift();
        //如今执行下一个事件
        try{
            event();
        }catch(e){
            reportError(e);
        }
    }
}

在浏览器端,setTimeout中的最小时间间隔是W3C在HTML标准中规定,规定要求低于4ms的时间间隔算为4ms。浏览器

任什么时候候,只要把一个代码包装成一个函数,并指定它在响应某个事件时执行,你就是在代码中建立了一个未来执行的模块,也由此在这个程序中引入了异步机制。服务器

js引擎并非独立运行的,它运行在宿主环境中,就是咱们所看到的Web浏览器,固然,随着js的发展,包括最近的Node,即是给js提供了一个在服务器端运行的环境。而且如今的js还嵌入到了机器人到电灯泡的各类各样的设配中。异步

可是这些全部的环境都有一个共同的“点”,即为都提供了一种机制来处理程序中的多个块的执行,且执行每一个块时调用JavaScript引擎,这种机制被称为事件循环。

js引擎自己并无时间的概念,只是一个按须要执行JavaScript任意代码片断的环境。

对于js中的回调,咱们最多见的就是链式回调和嵌套回调

咱们常常再ajax中嵌套ajax调用而后再嵌套ajax调用,这就是回调地狱,回调最大的问题就是控制反转,它会致使信任链的彻底断裂,为了解决回调中的控制反转问题,有些API提供了分离回调(一个用于成功通知,一个用于失败通知),例如ajax中的success函数和failure函数,这种状况下,API的出错处理函数failure()经常是能够省略的,若是没有提供的话,就是假定这个错误能够吞掉。

还有一种回调模式叫作“error-first"风格,其中回调的第一个参数保留用做错误对象,若是成功的话,这个参数就会被清空/置假。

回调函数是JavaScript异步的基础单元,可是随着JavaScript愈来愈成熟,对于异步领域的发展,回调已经不够用了。

Promise

Promise 是异步编程中的一种解决方案,最先由社区提出和实现,ES6将其写进了语言标准,统一了用法,原生提供了Promise对象。

Promise是一种封装和组合将来值的易于复用的机制。一种在异步任务中做为两个或更多步骤的流程控制机制,时序上的this-then-that. 假定调用一个函数foo(),咱们并不须要去关心foo中的更多细节,这个函数可能当即完成任务,也可能过一段时间才去完成。对于咱们来说,咱们只须要知道foo()何时完成任务,这样咱们就能够去继续执行下一个任务了,在传统的方法中,咱们回去选择监听这个函数的完成度,当它完成时,经过回调函数通知咱们,这时候通知咱们就是执行foo中的回调,可是使用promise时,咱们要作的是侦听来自foo的事件,而后在获得通知的时候,根据状况而定。

其中一个重要的好出就是,咱们能够把这个事件中的侦听对象提供给代码中多个独立的部分,在foo()完成的时候,他们均可以独立的获得通知:

var evt = foo();
    //让bar()侦听foo()的完成
    bar(evt);
    //让baz()侦听foo()的完成
    baz(evt);

上边的例子中,bar和baz中不须要去知道或者关注foo中的实现细节。并且foo也不须要去关注baz和bar中的实现细节。

一样的道理,在promise中,前面的代码片断会让foo()建立并返回一个Promise实例,并且在这个Promise会被传递到bar()和baz()中。因此本质上,promise就是某个函数返回的对象。你能够把回调函数绑定再这个对象上,而不是把回调函数当成参数传进函数。

const promise = doSomething();
    promsie.then(successCallback,failureCallback){
        
    }

固然啦,promise不像旧式函数将回调函数传递到两个处理函数中,并且会有一个优势:

  • 在JavaScript事件队列的本次tick运行完成以前,回调函数永远不会执行。
  • 经过.then形式添加的回调函数,甚至都在异步操做完成以后才被添加的函数,都会被调用。
  • 经过屡次调用.then,能够添加多个回调函数,他们会按照插入顺序而且独立运行。

可是,Promise最直接的好出就是链式调用。

doSomething().then(function(result) {
  return doSomethingElse(result);
})
.then(function(newResult) {
  return doThirdThing(newResult);
})
.then(function(finalResult) {
  console.log('Got the final result: ' + finalResult);
})
.catch(failureCallback);

而且在一个失败操做以后还能够继续使用链式操做,即便链式中的一个动做失败以后还能有助于新的动做继续完成。

在调用Promise中的resolve()和reject()函数时若是带有参数,那么他们的参数会被传递给回调函数。

Promise.resolve()和Promise.reject()是手动建立一个已经resolve或者reject的promise快捷方法。一般,咱们可使用Promise.resolve()去链式调用一个由异步函数组成的数组。例如:

Promise.resolve().then(func1).then(func2);

Promise.all()和Promise。race()是并行运行异步操做的两个组合式工具。

Promise.then()方法用来分别指定resolved状态和rejected状态的回调函数。传递到then中的函数被置入了一个微任务队列,而不是当即执行,这意味着它是在JavaScript事件队列的全部运行结束了,事件队列被清空以后才开始执行

let promise = new Promise(function(resolve, reject) {
  console.log('Promise');
  resolve();
});

promise.then(function() {
  console.log('resolved.');
});

console.log('Hi!');

// Promise
// Hi!
// resolved

Promise.then()方法返回一个Promise,它最多须要有两个参数:Promise的成功和失败状况的回调函数。

p.then(onFulfilled, onRejected);

p.then(function(value) {
   // fulfillment
  }, function(reason) {
  // rejection
});

onFulfilled:当Promise变成接受状态(fulfillment)时,该参数做为回调函数被调用。该函数有一个参数,即接受的值。

onRejected:当Promise变成拒绝状态时,该参数做为回调函数被调用。该函数有一个参数,即拒绝的缘由。

Promise的状态一旦改变,就永久保持该状态,不会再改变了。

Promise中的错误处理

通常的状况,咱们会在每次的Promise中抛出错误,在Promise中的then函数中的rejected处理函数会被调用,这是咱们做为错误处理的经常使用方法:

let p = new Promise(function(resolve,reject){
        reject('error');
    });
    
    p.then(function(value){
        success(value);
    },function(error){
        error(error)   
    }
)

可是一种更好的方式是使用catch函数,这样能够处理Promise内部发生的错误,catch方法返回的仍是一个Promise对象,后边还能够接着调用then方法。并且catch方法尽可能写在链式调用的最后一个,避免后边的then方法的错误没法捕获。

let p = new Promise(function(resolve,reject){
        reject('error');
    });
    
    p.then(function(value){
        success(value);
    }).catch(function(error){
        console.log('error');
    }}

Promise.finally()函数,该方法是ES2018引入标准的。指定无论Promise对象最后状态如何,都会执行的操做。finally方法的回调函数不接受任何参数,这意味着没有办法知道,前面的Promise状态究竟是fulfilled仍是rejected。这标明,finally方法里面的操做,是与状态无关的,不依赖于Promise的执行结果。


上述文章,若有错误,还请指正,谢谢!!!

参考

相关文章
相关标签/搜索