唯一比不知道代码为什么崩溃更可怕的事情是,不知道为什么一开始它是工作的!

在 ECMA 规范的最近几次版本里不断有新成员加入,尤其在处理异步的问题上,更是不断推陈出新。然而,我们在享受便利的同时,也应该了解异步到底是怎么一回事。

现在与将来

JavaScript 是单线程的,一次只能专注于一件事。如果浏览器只靠 JavaScript 引擎线程来完成所有工作,先不说能不能搞定,即使可以,那也会花费很长时间。幸好在浏览器里 JavaScript 引擎并不孤单,还有 GUI 渲染线程、事件触发线程、定时触发器线程、异步http请求线程等其它线程。这些线程之间的协作才有了我们看到的浏览器界面效果(远不止这些)。

(盗了一张图)

一个程序在执行过程中可能会有等待用户输入、从数据库或文件系统中请求数据、通过网络发送并等待响应,或是以固定时间间隔执行重复任务(比如动画)等情况。(这些情况,当下是无法得出结果的,但是一旦有了结果,我们知道需要去做些什么。)

JavaScript 引擎不是一个人在战斗,它把以上的任务交给其它线程,并计划好任务完成后要做的事,JavaScript 引擎又可以继续做自己的事了。从这里可以看出,一个程序的运行包括两部分,现在运行和将来运行。而现在运行和将来运行的关系正是异步编程的核心。

let params = {type:'asynchronous'}
let response = ajax(params,'http://someURL.com'); // 异步请求
if (!response) throw '无数据!';

以上代码肯定会抛错的,异步请求任务交出去之后,程序会继续运行下去。由于ajax(...) 是异步操作,即使立刻返回结果,当下的 response 也不会被赋值。一个是现在,一个是将来,两者本就不属于一个时空的。

事件循环

现在和将来是相对的,等将来的时刻到了,将来也就成为了现在。

JavaScript 引擎运行在宿主环境中,宿主环境提供了一种机制来处理程序中多个块的执行,且执行每个块时调用 JavaScript 引擎,这种机制被称为事件循环。即,JavaScript 引擎本身并没有时间的概念,只是一个按需执行 JavaScript 任意代码片段的环境。

“事件”(JavaScript 代码执行)调度总是由包含它的环境进行。

点击图片进入或点此进入:

一个 JavaScript 运行时包含了一个待处理的消息队列。每一个消息都关联着一个用以处理这个消息的函数。
在事件循环期间的某个时刻,运行时从最先进入队列的消息开始处理队列中的消息。为此,这个消息会被移出队列,并作为输入参数调用与之关联的函数。

while (queue.waitForMessage()) {queue.processNextMessage();
}

一旦有事件需要进行,事件循环就会运行,直到队列清空。事件循环的每一轮称为一个 tick。用户交互,IO 和定时器会向事件队列中加入事件。

(又盗了一张图)

任务队列

任务队列(job queue)建立在事件循环队列之上。(Promise 的异步特性就是基于任务。)

最好的理解方式,它是挂在事件循环队列的每个tick之后的一个队列。在事件循环的每个tick中,可能出现的异步动作不会导致一个完整的新事件添加到事件循环队列中,而会在当前 tick 的任务队列末尾添加一个项目(一个任务)。
即,由 Call Stack 生成的任务队列会紧随其后运行。

Promise.resolve().then(function promise1 () {console.log('promise1');
})
setTimeout(function setTimeout1 (){console.log('setTimeout1');Promise.resolve().then(function  promise2 () {console.log('promise2');})
}, 0)setTimeout(function setTimeout2 (){
console.log('setTimeout2');Promise.resolve().then(function  promise3 () {console.log('promise3');setTimeout(function setTimeout3 () {console.log('setTimeout3');})Promise.resolve().then(function promise4 () {console.log('promise4');})})
}, 0)
// promise1
// setTimeout1
// promise2
// setTimeout2
// promise3
// promise4
// setTimeout3

异步回调

被作为实参传入另一函数,并在该外部函数内被调用,用以来完成某些任务的函数,称为回调函数。回调函数经常被用于继续执行一个异步完成后的操作,它们被称为异步回调。立即执行的称之为同步回调。

回调函数是事件循环“回头调用”到程序中的目标,队列处理到这个项目的时候会运行它。

回调是 JavaScript 语言中最基础的异步模式。

生活中,我们喜欢和有条理的人打交道,因为我们的大脑习惯了这种思维模式。然而回调的使用打破了这种模式,因为代码的嵌套使得我们要在不同块间切换。嵌套越多,逻辑越复杂,我们也就越难理解和处理代码,尤其在表达异步的方式上。

(又盗了一张图)

除了嵌套的问题,异步回调还存在一些信任问题。

  • 回调性质的不确定
  • 调用回调方式不确定(没调用,重复调用等)
  • ......

针对第一点的建议是:永远异步调用回调,即使就在事件循环的下一轮,这样,所有回调都是可预测的异步调用了。
在理解这个建议之前,我们首先了解下控制反转,控制反转就是把自己程序一部分的执行控制交个某个第三方。

let a = 0; // A
thirdparty(() => {console.log('a', a);    // B
})
a++;    // C

A 和 C 是现在运行的,B 虽然代码是我们的,但是却受制于第三方,因为我们无法确定它是现在运行还是将来运行的。这里的回调函数可能是同步回调也可能是异步回调。a 是 0 还是 1,都有可能。

// 同步回调
const thirdparty = cb => {cb();
}
// 异步回调
const thirdparty = cb => {setTimeout(() => cb(), 0);
}

所以,永远异步调用回调,可预测。

function asyncify(fn) {let func = fn;let t = setTimeout(() => {t = null;if (fn) fn();}, 0);fn = null;return () => {if (t) {fn = func.bind(this, ...arguments);} else {func.apply(this, arguments);}}
}let a = 0;
thirdparty(asyncify(() => {console.log('a', a);
}))
a++;
// 1

JavaScript异步基础相关推荐

  1. JavaScript异步精讲,让你更加明白Js的执行流程!

    JavaScript异步精讲,让你更加明白Js的执行流程! 问题点 什么是单线程,和异步有什么关系 什么是 event-loop jQuery的Deferred Promise 的基本使用和原理 as ...

  2. 写给初学者的JavaScript异步编程和背后思想

    导读: 对于接触JavaScript这门编程语言没有多久的本菜鸡而言,在相当长的一段时间内,我都完全无法理解这门语言中的异步编程,不明白什么叫异步编程以及为什么需要异步编程.为什么顺序执行程序就不行了 ...

  3. Javascript异步编程之一异步原理

    本系列的例子主要针对node.js环境,但浏览器端的原理应该也是类似的. 本人也是Javascript新手,把自己这段时间学习积累的要点总结下来,希望可以对同样在学习Javascript/node.j ...

  4. 网页javascript加载不出_写给初学者的JavaScript异步编程和背后思想

    导读:对于接触JavaScript这门编程语言没有多久的本菜鸡而言,在相当长的一段时间内,我都完全无法理解这门语言中的异步编程,不明白什么叫异步编程以及为什么需要异步编程.为什么顺序执行程序就不行了呢 ...

  5. JavaScript异步编程原理

    众所周知,JavaScript 的执行环境是单线程的,所谓的单线程就是一次只能完成一个任务,其任务的调度方式就是排队,这就和火车站洗手间门口的等待一样,前面的那个人没有搞定,你就只能站在后面排队等着. ...

  6. 《HTML5+JavaScript动画基础》——2.4 JavaScript对象

    本节书摘来自异步社区<HTML5+JavaScript动画基础>一书中的第2章,第2.4节,作者:[美]Billy Lamberta , Keith Peters著,更多章节内容可以访问云 ...

  7. JavaScript零基础入门 13:DOM规范中的MutationObserver接口

    目录 一.MutationObserver接口 二.MutationObserver基本用法 1.observe()方法 2.回调与MutationRecord 3.disconnect()方法 4. ...

  8. JavaScript入门基础知识

    JavaScript入门基础学习 1.三种引用JavaScript的方式 1.1行内式 <input type = "button" value="点我试试&quo ...

  9. JavaScript 异步编程--Generator函数、async、await

    JavaScript 异步编程–Generator函数 Generator(生成器)是ES6标准引入的新的数据类型,其最大的特点就是可以交出函数的执行的控制权,即:通过yield关键字标明需要暂停的语 ...

最新文章

  1. MetaPhlAn2-增强版宏基因组分类谱工具-一条命令获得宏基因组物种组成
  2. crc16 modbus php代码,crc16 - 产生Modbus RTU格式的CRC码
  3. 微信红包的支撑架构原理是什么?
  4. Css 选择器 算法 规则
  5. 浙大JAVA实验题12_2019浙大计算机考研机试模拟赛(2)——概念专题
  6. 第七章 yaml格式
  7. 视频录制软件进行电脑屏幕录像的使用方法
  8. 新网银行模型竞赛点评-小微风控算法大赛-早期风险识别
  9. HBuilder手机Iphone运行提示“未受信用的企业级开发者”
  10. 贪吃的九头龙-----树形dp
  11. mysql发音_mysql应该怎么念?
  12. vue3父传子,子传父
  13. WhatsApp网页版登陆,WhatsApp官网入口
  14. GPU巨头英伟达遭“​毁灭性”网络攻击、头号恶意软件关闭其僵尸网络基础设施|2月28日全球网络安全热点
  15. Neural Collaborative Filtering复现
  16. js根据value值删除元素
  17. ilitek win10 触摸屏驱动_德国布拉本达(Brabender)触摸屏维修常见故障_触摸屏维修吧...
  18. button按钮居中
  19. 日期 时间格式转换 时间过滤器 moment
  20. 北大计算机结构专业排名,中国大学专业排名出炉,涵盖509个专业,北大A+专业数遥遥领先...

热门文章

  1. linux64位ioremap函数,linux操作系统中的ioremap函数详解
  2. linux 扫描i2c端口,s3c2440用I2C接口访问EEPROM
  3. android高度混淆,android – TextView与ImageSpan混淆行高
  4. word2vec模型评估_【新书】从Word2Vec到BERT的自然语言处理嵌入进展,附下载
  5. 无法安装php-fpm,PHP编译安装后无法启动PHP-FPM
  6. python绘制三维曲线图_Python基于matplotlib实现绘制三维图形功能示例
  7. thinkphp项目mysql类关系_ThinkPHP数据库与模型
  8. java字符动画思路_【轻松一刻】Java制作字符动画
  9. linux上离线安装bcp,无法在Linux上安装Pyodbc
  10. html怎么防止修改数据,HTML防数据采集