单线程JavaScript实现异步过程详解

前两天硬着头皮在部门内部做了一次技术分享,主题如题。索性整理成文章留个纪念!

要了解异步实现,首先我们得先了解:

同步 & 异步

同步:会逐行执行代码,会对后续代码造成阻塞,直至代码接收到预期的结果之后,才会继续向下执行任务。

异步:调用之后先不管结果,继续向下执行任务。

网上各种文章对同步和异步的解释也不外如是,但是看文字总是有点晦涩难懂!我就生活化的来比拟一下这两个概念吧!

就好比请人吃饭:

比如你要请两个人吃饭,一个是巴菲特,由于他是举世瞩目股神想请他吃饭的人从这里排到了法国,你为表诚意,你会精心打扮自己,然后租一架飞机亲自去美国,请他跟你吃顿特色菜...那么为了请他吃个烤腰子,你全程都在为些事费心费力,投入大量的精力!

所以,也就阻塞了你干别的事情,是的,这就是同步!

请人吃顿饭就这么难吗?当然,也没有那么难!不信,你请我吃饭试试:

如果你想请我吃饭,那你只需要打个电话通知我一声:喂,今天晚上请你吃个海底捞啊!我:好啊!然后你不要来接我,到了点我自己去了!期间,你该干嘛就去干嘛!

看,其他也很简单嘛?瞧,这就是异步!

那么回到代码层面:

同步代码:(代码片段1)

function someTime() {
  let s = Date.now();
  while(true) {
    if (Date.now() - s > 2000) {
      console.log(2)
      break;
    }
  }
}
console.log(1);
someTime();
console.log(3);
// 其打印顺序:1 ...(2秒以后)... 2 3

异步代码:(代码片段2)

function someTime() {
  setTimeout(() => {
    console.log(2);
  }, 2000)
}

console.log(1);
someTime();
console.log(3);

// 其打印顺序:1 3 ...(2秒以后)... 2

看看,同步代码,当执行这种耗时操作时,就会停在原地,一定要等待这时间过去之后才会执行后面的代码!而异步代码,后面的执行完全不受影响...

JavaScript单线程

众所周知JavaScript是单线程的,所谓单线程是指程序执行时,所走的程序路径按照连续顺序排下来,前面的必须处理好,后面的才会执行!这个解释跟【同步】的解释如出一辙!

如此看起来异步编程对于单线程而言似乎并非正统,甚至有点矛盾。然而,通过刚才的例子,我们发现,JavaScript是真的实现了异步编程的!为啥加了个setTimeout()不能不阻塞了呢?按单线程的执行的话那如下代码会是怎么样的呢?

function timeOut() {
  setTimeout(() => {
    console.log('timeOut');
  }, 0)
}
function someTime() {
  let s = Date.now();
  while(true) {
    if (Date.now() - s > 2000) {
      console.log('some Time')
      break;
    }
  }
}
console.log(1);
timeOut();
someTime();
console.log(3);

如果是以单线程那种解释来执行的话,这个打印顺序应该是:1 - time Out - some Time - 3才对!然而,其真正的执行结果却是:1 - some Time - 3 - time Out

为什么?浏览器的多线程

JavaScript是脚本语言,它需要在一个宿主环境里才能运行,显然我们接触较多的宿主环境就是--浏览器!虽说JavaScript是单线程的,然而浏览器却不是!

如图所求,JavaScript引擎线程称为主线程,它负责解析JavaScript代码;其他可以称为辅助线程,这些辅助线程便是JavaScript实现异步的关键了!

如(代码片段2):主线程负责自上而下顺序执行,当遇到setTimeout函数后,便将其交给定时器线程去执行,自己继续执行下面的代码!从而达到异步的目的。

不仅如此,更关键的是:

任务队列

当定时器线程计时执行完之后,会将回调函数放入任务队列中!

当这些任务加入到任务队列后并不会立即执行,而是处于等候状态!等主线程处理完了自己的事情后,才来执行任务队列中任务!

这个过程我感觉像是古代嫔妃被翻了牌子后,就需要在自己寝宫里精心准备,等待皇上批完凑折后的驾临...(哦,别想歪了!)

宏任务 & 微任务

然而,异步任务却又分为两种:一种叫“宏任务”(MacroTask 或者 Task),一种叫“微任务”(MicroTask)!

这又是两个啥玩意呢?

光看这个依然晦涩难懂,那我们来看一段代码吧!

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

这段代码的执行结果:1 - 4 - 3 - 2。LOOK!2是最后打印的,哪怕该计时器的时间设置为0。通过之前的同步和异步的解释,1和4先于2打印应该很好理解了,但同样是异步,3也优先于2打印,这又是为什么呢?答案就是因为 setTimeout属于宏任务,而Promise属于微任务!

好吧~ 这就是宏任务和微任务的差别...什么?没懂?

微任务是皇后所生的,是嫡子;而宏任务是某个小妃子所生, 是庶子!你说选太子的时候谁优先?

浏览器的Event Loop

1.执行全局Script同步代码,形成一个执行栈;

2.在执行代码时当遇到如上异步任务时便会按上文所描述的将宏任务回调加入宏任务队列,微任务回调加入微任务队列;

3.然而,回调函数放入任务队列后也不是立即执行;会等待执行栈中的同步任务全部执行完清空了栈后引擎才能会去任务队列检查是否有任务,如果有那便会将这些任务加入执行栈,然后执行!

4.执行栈清空后,会先去检查微任务队列是否有任务,逐一将其任务加入执行栈中执行,期间如果又产生了微任务那继续将其加入到列队末尾,并在本周期内执行完,直到微任务队列的任务全部 清空,执行栈也清空后,再去检查宏任务队列是否有任务,取到队列队头的任务放入到执行栈中执行,其他可能又会产生微任务,那当本次执行栈中的任务结果清空后又会去检查微任务队列...

5.引擎会循环执行如上步骤,这就是Event Loop!

又要上代码了:

console.log('start');
setTimeout(() => {
  console.log('time1');
  Pormise.resolve().then(() => {
    console.log('promise1');
  })
}, 0);
setTimeout(() => {
  console.log('time2');
  Pormise.resolve().then(() => {
    console.log('promise2');
  })
}, 0);
Pormise.resolve().then(() => {
  console.log('promise3');
});
console.log('end');

这段代码的打印顺序:

start - end - promise3 - timer1 - promise1 - timer2 - promise2

据说:node 10.x版本上面的输入结果会是:

start - end - promise3 - timer1 - timer2 - promise1 - promise2

node 11.x版本以后改了,输出跟浏览器输出一致了!

Web Worker

HTML5中支持了Web Worker,使得能够同时执行两段JS了,那是不是就是说JS实现了“多线程”了呢?我们来看看Web Worker的官方解释:

通过使用Web Workers,Web应用程序可以在独立于主线程的后台线程中,运行一个脚本操作。这样做的好处是可以在独立线程中执行费时的处理任务,从而允许主线程(通常是UI线程)不会因此被阻塞/放慢。

独立线程,看似像是实现了“多线程”,然而他是独立于主线程,也就是主线程依然是那个主线程没有变!虽然你大妈已经不是你大妈了,但是你大爷还是你大爷!JS单线程的本质依然没有变!

WebWorker是向浏览器申请一个子线程,该子线程服务于主线程,完全受主线程控制。

Web Worker注意事项:

写了一个demo:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Web Worker</title>
</head>
<body>
  <button onclick="startWorker()">开始</button>
  <button onclick="stopWorker()">停止</button>
  <button onclick="updateNum()">在运行时点击</button>
  <div id="output"></div>
  <div id="num"></div>

  <script id="worker" type="app/worker">
    function updateSync() {
      for (let i = 0; i < 10000000000; i++) {
        if (i % 100000 === 0) {
          postMessage(i);
        }
      }
    }
    updateSync();
  </script>

  <script>
    let worker;
    function startWorker() {
      let blob = new Blob([document.querySelector('#worker').textContent]);
      let url = window.URL.createObjectURL(blob);
      console.log(url);
      worker = new Worker(url);

      worker.onmessage = function(e) {
        document.getElementById('output').innerHTML = e.data;
      }
    }

    function stopWorker() {
      if (worker) {
        worker.terminate();
      }
    }

    let num = 0;
    function updateNum() {
      num++;
      document.getElementById('num').innerHTML = num;
    }
  </script>
</body>
</html>

这段代码可以稍微解释一下Web Worker的用途之一 --执行费时的处理任务吧!

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • 浅谈如何优雅处理JavaScript异步错误

    1. try/catch try/catch基本上是大家最常和async/await一起使用的,基本上我们会用它去包围大部分的异步方法.await关键字后面的promise一旦reject了,就会抛出一个异常错误. run(); async function run() { try { await Promise.ject(new Error('Oops!')); } catch (err) { console.error(error.message); } } try/catch同样也可以处理

  • 解决Angularjs异步操作后台请求用$q.all排列先后顺序问题

    最近我在做angularjs程序时遇到了一个问题 1.页面有很多选择框,一个选择框里面有众多的选择项,和一个默认选定的项,像下面这样(很多选择框,不只一个): 2.众多的选项要从后台接口得到,默认项从另一个后台接口得到,这就需要$promise.then()操作 3.而多个$promise.then()属于异步操作,先后顺序不是一定的,如果先得到众多选项,后得到默认值,显示就没有问题,如果顺序颠倒,默认项就会为空,这不是我想要的 4.这就需要众多选项的后台请求都获得完,才去后台请求默认值, 就用

  • JS多个异步请求 按顺序执行next实现解析

    在js里面,偶尔会遇见需要多个异步按照顺序执行请求,又不想多层嵌套,,这里和promise.all的区别在于,promise或者Jquery里面的$.when 是同时发送多个请求,一起返回,发出去的顺序是一起:这里是按照顺序发请求 首先创建一个迭代器,接收任意多个函数参数 function nextRegister(){ var args = arguments; var count = 0; var comm = {}; function nextTime(){ count++; if(cou

  • Nodejs异步流程框架async的方法

    Async的简单介绍: Async是一个流程控制工具包,提供了直接而强大的异步功能.基于Javascript为Node.js设计,同时也可以直接在浏览器中使用.Async提供了大约20个函数,包括常用的map, reduce, filter, forEach等,异步流程控制模式包括,串行(series),并行(parallel),瀑布(waterfall)等. https://github.com/caolan/async 我们常用的是以下四种: 串行无关联 串行有关联 并行无关联 智能控制 1

  • JS中async/await实现异步调用的方法

    async/await多个函数关联调用 async/await使得异步代码看起来像同步代码 async函数会隐式地返回一个promise,而promise的reosolve值就是函数return的值 Async/Await不需要写.then,不需要写匿名函数处理Promise的resolve值,也不需要定义多余的data变量,还避免了嵌套代码 async声明一个异步函数 await只能在async函数中使用,后面跟一个promise对象 所以在模拟异步调用函数时,函数体内返回promise as

  • JS+html5实现异步上传图片显示上传文件进度条功能示例

    本文实例讲述了JS+html5实现异步上传图片显示上传文件进度条功能.分享给大家供大家参考,具体如下: <html> <head> </head> <body> <p> emo_album_id:<input type="text" name="emo_album_id" id="emo_album_id" value='1'> </p> <p> na

  • JS异步执行结果获取的3种解决方式

    前言 JS异步执行机制具有非常重要的地位,尤其体现在回调函数和事件等方面. 但异步有时候很方便,有时候却很让人恼火,下面来总结一下异步执行结果获取的方法 回调 这是最传统的方法了,也是最简单的,如下代码 function foo(cb) { setTimeout(function() { cb(1); // 通过参数把结果返回 }, 2000); } foo(function(result) { // 调用foo方法的时候,通过回调把方法返回的数据取出来 console.log(result);

  • 单线程JavaScript实现异步过程详解

    前两天硬着头皮在部门内部做了一次技术分享,主题如题.索性整理成文章留个纪念! 要了解异步实现,首先我们得先了解: 同步 & 异步 同步:会逐行执行代码,会对后续代码造成阻塞,直至代码接收到预期的结果之后,才会继续向下执行任务. 异步:调用之后先不管结果,继续向下执行任务. 网上各种文章对同步和异步的解释也不外如是,但是看文字总是有点晦涩难懂!我就生活化的来比拟一下这两个概念吧! 就好比请人吃饭: 比如你要请两个人吃饭,一个是巴菲特,由于他是举世瞩目股神想请他吃饭的人从这里排到了法国,你为表诚意,

  • JavaScript的异步ajax详解

    目录 一级目录 二级目录 三级目录 HTTP协议 请求消息结构 请求方法 响应头信息 响应状态码 AJAX AJAX=AsynchronousJavaScriptandXML(异步的JavaScript[JSON]和XML). XMLHttpRequest对象 XMLHttpRequest请求 XMLHttpRequest响应 XMLHttpRequest响应状态 XML XML语法规则 XML解析 JSON JSON语法规则 JSON文件读取 JSON解析 JSONP 服务端JSONP格式数据

  • JavaScript处理解析JSON数据过程详解

    JSON (JavaScript Object Notation)一种简单的数据格式,比xml更轻巧. JSON 是 JavaScript 原生格式,这意味着在 JavaScript 中处理 JSON 数据不需要任何特殊的 API 或工具包. JSON的规则很简单: 对象是一个无序的"'名称/值'对"集合.一个对象以"{"(左括号)开始,"}"(右括号)结束.每个"名称"后跟一个":"(冒号):"

  • JavaScript代码异常监控实现过程详解

    这篇文章主要介绍了JavaScript代码异常监控实现过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 JavaScript异常一般有两方面:语法错误和运行时错误.两种错误的捕获和处理方式不同,从而影响具体的方案选型.通常来说,处理JS异常的方案有两种:try...catch捕获 和 window.onerror捕获.以下就两种方案分别分析各自的优劣. 虽然语法错误本应该在开发构建阶段使用测试工具避免,但难免会有马失前蹄部署到线上的时候.

  • 使用纯前端JavaScript实现Excel导入导出方法过程详解

    公司最近要为某国企做一个**统计和管理系统, 具体要求包含 Excel导入导出根据导入的数据进行展示报表图表展示(包括柱状图,折线图,饼图),而且还要求要有动画效果,扁平化风格Excel导出,并要提供客户端来管理Excel 文件... 要求真多! 现在总算是完成了,于是将我的经验分析出来. 在整个项目架构中,首先就要解决Excel导入的问题. 由于公司没有自己的框架做Excel IO,就只有通过其他渠道了. 嗯,我在github上找到了一个开源库xlsx,通过npm方式来安装. npm inst

  • javascript中Promise使用详解

    目录 一.首先,要知道为什么要用Promise语法? 二.接着,来了解一下回调地狱(Callback Hell) 三.最后,也是本章的重头戏,Promise的基本使用 (一) resolve函数 (二) rejected函数 (三)Promise的API 1. then 2. catch 3. finally 4. Promise.all 5. Promise.race 四.最后 前言: 做过前端开发的都知道,JavaScript是单线程语言,浏览器只分配给JS一个主线程,用来执行任务,但是每次

  • JavaScript实现瀑布流布局详解

    目录 需求 思路 代码实现 实现效果 问题和修正 修正后效果 总结 需求 所谓瀑布流布局,就是含有若干个等宽的列,每一列分别放置图片.视频等,放置的元素都是等宽的,因此可能是不等高的.新的元素到来时,会插入高度较低的那一列,这样形成参差的.视觉上像瀑布一样的布局. 这里简化一下,只要两列等宽布局展示图片即可. 思路 两列布局,直接使用flex布局实现即可.不过,这里不能设置align-items为center,如果设置了将会使图片列居中显示,不符合瀑布流的视觉效果.我设置left和right两列

  • JavaScript async/await使用详解

    目录 一.简介 二.async 三.await 四.案例 附-直接量/字面量 一.简介 async/await是ES20717引入的,主要是简化Promise调用操作,实现了以异步操作像同步的方式去执行,async外部是异步执行的,同步是await的作用. 二.async async,英文意思是异步,当函数(包括函数语句.函数表达式.Lambda表达式)前有async关键字的时候,并且该函数有返回值,函数执行成功,那么该函数就会调用Promise.resove()并隐式的返回一个Promise对

  • 基于JavaScript表单脚本(详解)

    什么是表单? 一个表单有三个基本组成部分: 表单标签:这里面包含了处理表单数据所用CGI程序的URL以及数据提交到服务器的方法. 表单域:包含了文本框.密码框.隐藏域.多行文本框.复选框.单选框.下拉选择框和文件上传框等. 表单按钮:包括提交按钮.复位按钮和一般按钮:用于将数据传送到服务器上的CGI脚本或者取消输入,还可以用表单按钮来控制其他定义了处理脚本的处理工作. JavaScript与表单间的关系:JS最初的应用就是用于分担服务器处理表单的责任,打破依赖服务器的局面,尽管目前web和jav

  • C++ 线程(串行 并行 同步 异步)详解

    C++  线程(串行 并行 同步 异步)详解 看了很多关于这类的文章,一直没有总结.不总结的话就会一直糊里糊涂,以下描述都是自己理解的非官方语言,不一定严谨,可当作参考. 首先,进程可理解成一个可执行文件的执行过程.在ios app上的话我们可以理解为我们的app的.ipa文件执行过程也即app运行过程.杀掉app进程就杀掉了这个app在系统里运行所占的内存. 线程:线程是进程的最小单位.一个进程里至少有一个主线程.就是那个main thread.非常简单的app可能只需要一个主线程即UI线程.

随机推荐