一次搞懂-JavaScript之异步编程

九旬
• 阅读 1459

一次搞懂-JavaScript之异步编程

前言

异步,就是非同步....

这节内容可能会有点枯燥,但是却是 JavaScript 中非常重要的概念,非常有必要去学习。

目的

  • 提升开发效率,编写易维护的代码

引子问题

  • 请求时候为什么页面卡死??
$.ajax({
  url: "www.xx.com/api",
  async: false, // true
  success: function(result) {
    console.log(result);
  },
});
  • 为什么数据更新了,DOM 却没有更新??
// 异步批量更新DOM(vue-nextTick)
// <div id="app">{{num}}</div>
new Vue({
  el: "#app",
  data: {
    num: 0,
  },
  mounted() {
    let dom = document.getElementById("app");
    while (this.num !== 100) {
      this.num++;
    }
    console.log("Vue num=" + this.num, "DOM num=" + dom.innerHTML);
    // Vue num=100,DOM num=0
    // nextTick or setTimeout
  },
});

产生异步的原因

原因:单线程(一个时间点,只做一件事),浏览器的 JS 引擎是单线程导致的。

单线程是指在 JS 引擎中负责解释和执行 IavaScript 代码的线程只有一个,不妨叫它主线程。

所谓单线程,就是指一次只能完成一件任务。如果有多个任务,就必须排队,前面一个任务完成再执行后面一个任务。

先看看一下浏览器内核的线程图: 一次搞懂-JavaScript之异步编程

其中,渲染线程和 JS 线程互斥

假设有两个函数,一个修改一个删除,同时操作一个 DOM 节点,假如有多个线程的话,两个线程一起执行,肯定就死锁了,就会有问题。

为什么 JS 要设计为单线程,因为浏览器的特殊环境。

单线程的优缺点:

这种模式的好处是实现起来比较简单,执行环境相对单纯;坏处是只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。常见的浏览器无响应(假死),往往就是因为某一段 Javascript 代码长时间运行(比如死循环),导致整个页面卡在这个地方,其他任务无法执行。

常见的堵塞(死循环):

while (true) {}

JS 在设计之初就以运行在浏览器中的脚本语言,所以也不想搞得这么复杂,就设计成了单线程,也就是,一个时间点,只能做一件事。

为了解决单线程堵塞这个缺点:产生了异步。

拿吃泡面举例:

  • 同步:买泡面=》烧水(盯着)=》煮面=》吃泡面
  • 异步:买泡面=》烧水(水开了热水壶响-回调)=》看电视=》煮面(面好了热水壶响-回调)=》看电视=》熟了叫我=》吃泡面

看电视就是异步操作,热水壶响,就是回调函数。

异步编程

JS 中大多的代码都是同步执行的,只有极个别的函数是异步执行的,异步执行的代码,则需要异步编程。

异步代码

setTimeout(() => {
  console.log("log2");
}, 0);
console.log("log1");
// ?? log1 log2

异步代码的特点:不是立即执行,而是需要等待,在未来的某一个时间点执行。

同步代码 异步代码
<script>代码 网络请求(Ajax)
I/O 操作 定时器(setTimeout、setInterval)
渲染操作 Promise(then)
async/await

回调函数

异步代码最常见的写法就是使用回调函数。

  • HTTP 网络请求(请求成功、识别后执行 xx 操作)
  • DOM 事件绑定机制(用户触发事件后执行 xx 操作)
  • 定时器(setTimeout、setInterval)(在达到设定时间后执行 xx 操作)
// 注意到click方法中是一个函数而不是一个变量
// 它就是回调函数
$("#btn_1").click(function() {
  alert("Btn 1 Clicked");
});
// 或者
function click() {
  // 它就是回调函数
  alert("Btn 1 Clicked");
}
$("#btn_1").click(click);

回调函数的缺点也很明显,容易产生回调地狱: 一次搞懂-JavaScript之异步编程

异步编程的三种方式

  • callback
function getOneNews() {
  $.ajax({
    url: topicsUrl,
    success: function(res) {
      let id = res.data[0].id;
      $.ajax({
        url: topicOneUrl + id,
        success: function(ress) {
          console.log(ress);
          render(ress.data);
        },
      });
    },
  });
}
  • promise
function getOneNews() {
  axios
    .get(topicsUrl)
    .then(function(response) {
      let id = response.data.data[0].id;
      return axios.get(topicOneUrl + id);
    })
    .then((res) => {
      render(res.data.data);
    })
    .catch(function(error) {
      console.log(error);
    });
}
  • async/await
async function getOneNews() {
  let listData = await axios.get(topicsUrl);
  let id = listData.data.data[0].id;
  let data = await axios.get(topicOneUrl + id);
  render(data.data.data);
}

在线预览

预览地址:http://jsrun.net/s43Kp/embedded/all/light

问题??

如果多个异步代码同时存在,那么执行顺序应该是怎样的?那个先执行、那个后执行了?

宏任务和微任务

异步代码的划分,异步代码分宏任务和微任务。

宏任务(不着急) 微任务(着急)
<script>整体代码 Promise
setTimeout/setInterval

事件循环(Event loop)

一次搞懂-JavaScript之异步编程

执行顺序:

  1. 执行整体代码<script>(宏任务)
  2. 执行所有微任务
  3. 执行一个宏任务
  4. 执行渲染线程
  5. 2->3->2->3...依次循环(在 2、3 步中又创建了新的宏、微任务)

重复从宏任务和微任务队列里拿出任务去执行。

总结

因为浏览器设计的原因,JS 线程和渲染线程互斥,所以 JS 线程被设计成了单线程。

因为单线程执行一些操作(如网络请求)时有堵塞的问题,所有产生了异步。

因为有了异步,所以产生了异步编程,从而有了回调函数。

因为回调函数写多了会产生回调地狱,所有又有了解决回调地狱的 Promise 写法

自 ES7 标准后有了比 Promise 更加优雅的写法 ———— async/await 写法,也是异步编程的最终解决方法。

因为 JS 的代码分为同步和异步代码,同步代码的执行顺序不必多说,自上而下的执行。

但是如果有多个异步的代码,他的执行顺序又是怎么的呢??

为了解决多个异步代码的执行顺序问了,有了事件循环(EventLoop),将异步任务区分为宏任务、微任务,依据规则依次执行。

至此 完!

练习

console.log("script start");
setTimeout(function() {
  console.log("timeout1");
}, 10);
new Promise((resolve) => {
  console.log("promise1");
  resolve();
  setTimeout(() => console.log("timeout2"), 10);
}).then(function() {
  console.log("then1");
});
console.log("script end");

写出 log 的输出结果,并说出理由。

参考

来自九旬的原创文章

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Easter79 Easter79
2年前
swap空间的增减方法
(1)增大swap空间去激活swap交换区:swapoff v /dev/vg00/lvswap扩展交换lv:lvextend L 10G /dev/vg00/lvswap重新生成swap交换区:mkswap /dev/vg00/lvswap激活新生成的交换区:swapon v /dev/vg00/lvswap
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Stella981 Stella981
2年前
Android So动态加载 优雅实现与原理分析
背景:漫品Android客户端集成适配转换功能(基于目标识别(So库35M)和人脸识别库(5M)),导致apk体积50M左右,为优化客户端体验,决定实现So文件动态加载.!(https://oscimg.oschina.net/oscnet/00d1ff90e4b34869664fef59e3ec3fdd20b.png)点击上方“蓝字”关注我
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这
九旬
九旬
Lv1
男 · 北京 · 前端
种一棵树最好的时间在十年前,其次是现在。
文章
6
粉丝
3
获赞
7