JS线程是单线程运行机制,就是自己按顺序做自己的事,浏览器线程用于交互和控制,JS可以操作DOM元素,

说起JS中的异步时,我们需要注意的是,JS中其实有两种异步,一种是基于浏览器的异步IO,比如Ajax,另外一种是基于计时方法setTimeout和setInterval的异步。

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

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

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

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

eventloop是一个用作队列的数组,eventloop是一个一直在循环执行的,循环的每一轮成为一个tick,在每一个tick中,如果队列中有等待事件,那么就会从队列中摘取下一个事件进行执行,这些事件就是我们之前的回调函数。现在ES6精确指定了事件循环的工作细节,这意味着在技术上将其纳入了JavaScript引擎的势力范围,而不只是由宿主环境决定了,主要的一个原因是ES6中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的执行结果。


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

参考

从JavaScript的事件循环到Promise的更多相关文章

  1. 对javascript EventLoop事件循环机制不一样的理解

    前置知识点: 浏览器原理,浏览器内核5种线程及协作,JS引擎单线程设计推荐阅读: 从浏览器多进程到JS单线程,JS运行机制最全面的一次梳理 [FE]浏览器渲染引擎「内核」 js异步编程,Promise ...

  2. JavaScript的事件循环机制浅析

    前言 JavaScript是一门单线程的弱类型语言,但是我们在开发中,经常会遇到一些需要异步或者等待的处理操作. 类似ajax,亦或者ES6中新增的promise操作用于处理一些回调函数等. 概念 在 ...

  3. [译] JavaScript 的事件循环

    译者注 本译文基本是按原文的意思来翻译,但对于 JavaScript 的事件循环,个人感觉还是 Philip Roberts 的视频讲解更形象些,思路和本文大致相同,不过他把事件表理解为 Web AP ...

  4. Javascript的事件模型和Promise实现

    1. Javascript的运行时模型——事件循环 JS的运行时是个单线程的运行时,它不像其他编程语言,比如C++,Java,C#这些可以进行多线程操作的语言.当它执行一个函数时,它只会一条路走到黑, ...

  5. 【运行机制】 JavaScript的事件循环机制总结 eventLoop

    0.从个例子开始 //code-01 console.log(1) setTimeout(() => { console.log(2); }); console.log(3); 稍微有点前端经验 ...

  6. javascript的事件循环机制

    JavaScript是一门编程语言,既然是编程语言那么就会有执行时的逻辑先后顺序,那么对于JavaScript来说这额顺序是怎样的呢? 首先我们我们需要明确一点,JavaScript是单线程语言.所谓 ...

  7. 深入理解JavaScript的事件循环(Event Loop)

    一.什么是事件循环 JS的代码执行是基于一种事件循环的机制,之所以称作事件循环,MDN给出的解释为 因为它经常被用于类似如下的方式来实现 while (queue.waitForMessage()) ...

  8. 从一道题浅说 JavaScript 的事件循环

    最近看到这样一道有关事件循环的前端面试题: //请写出输出内容 async function async1() { console.log('async1 start'); await async2( ...

  9. 深入理解JavaScript事件循环机制

    前言 众所周知,JavaScript 是一门单线程语言,虽然在 html5 中提出了 Web-Worker ,但这并未改变 JavaScript 是单线程这一核心.可看HTML规范中的这段话: To ...

随机推荐

  1. MySQL架构篇(一)

    MySQL复制解决了什么问题? 1.实现在不同服务器上的数据分布 2.利用二进制日志增量进行 3.不需要太多的带宽 4.但是使用基于行的复制在进行大批量的更改时会对带宽带来一定的压力,特别是跨IDC环 ...

  2. Phpstrom操作git

    1.PHPstrom操作git[上传] 2.提交代码到仓库 2. 3.使用git bash上传代码仓库的代码到远程服务器 代开git bash进入到项目所在的目录,输入命令$  git push .上 ...

  3. (转载)windows下mysql忘记密码

    Mysql版本:5.1 1. 首先检查mysql服务是否启动,若已启动则先将其停止服务,可在开始菜单的运行,使用命令: net stop mysql 打开第一个cmd窗口,切换到mysql的bin目录 ...

  4. java I/O框架 (一)总览

    一.前言 java io框架非常庞大,各种功能的类让人目不暇接,为了系统学习io框架,搜集了各种资料,整理出这篇文章,尽可能详细的讲述java io框架,其中会牵扯到许多信息,不仅包括框架内各种类的方 ...

  5. 1.3 PCI总线的存储器读写总线事务

    总线的基本任务是实现数据传送,将一组数据从一个设备传送到另一个设备,当然总线也可以将一个设备的数据广播到多个设备.在处理器系统中,这些数据传送都要依赖一定的规则,PCI总线并不例外. PCI总线使用单 ...

  6. 项目总结2——mybatis配置的理解

    之前的项目基本上都是用mongodb,以至于mysql相关的知识异常薄弱,这次连续一个半月的加班,总算是实际用到了mysql,也使自己对mysql了解的更多,对mybatis了解的更多,这里就说一说经 ...

  7. 网页版Rstudio︱RStudio Server多人在线协作开发

    网页版Rstudio︱RStudio Server多人在线协作开发 想了解一下RStudio Server,太给力的应用,可以说成是代码分布式运行,可以节省时间,放大空间. RStudio是一个非常优 ...

  8. canvas动画:自由落体运动

    经过前面的文章,我们已经能够在canvas画布上画出各种炫酷的图形和画面,但是这些画面都是禁止的,怎么样才能让他们动起来呢? 如何绘制基本图形可以参考:canvas基本图形绘制 如何对基本图形移动旋转 ...

  9. C#技术点--修改系统时间

    C#的System.DateTime类提供了对日期时间的封装,用它进行时间的转换和处理很方便,但是我没有在其中找到任何可以用来修改系统时间的成员.用过VC.VB等的朋友可能知道,我们可以调用Win32 ...

  10. PyCharm运行报编码错误

    运行报如下错误: SyntaxError: Non-ASCII character '\xe8' in file /home/ubuntu/code/201803091253-text.py on l ...