资讯专栏INFORMATION COLUMN

JavaScript异步基础

hidogs / 1141人阅读

摘要:异步回调被作为实参传入另一函数,并在该外部函数内被调用,用以来完成某些任务的函数,称为回调函数。回调函数经常被用于继续执行一个异步完成后的操作,它们被称为异步回调。回调函数是事件循环回头调用到程序中的目标,队列处理到这个项目的时候会运行它。

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

在 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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/101760.html

相关文章

  • 总结javascript基础概念(二):事件队列循环

    摘要:而事件循环是主线程中执行栈里的代码执行完毕之后,才开始执行的。由此产生的异步事件执行会作为任务队列挂在当前循环的末尾执行。在下,观察者基于监听事件的完成情况在下基于多线程创建。 主要问题: 1、JS引擎是单线程,如何完成事件循环的? 2、定时器函数为什么计时不准确? 3、回调与异步,有什么联系和不同? 4、ES6的事件循环有什么变化?Node中呢? 5、异步控制有什么难点?有什么解决方...

    zhkai 评论0 收藏0
  • 夯实基础-JavaScript异步编程

    摘要:调用栈被清空,消息队列中并无任务,线程停止,事件循环结束。不确定的时间点请求返回,将设定好的回调函数放入消息队列。调用栈执行完毕执行消息队列任务。请求并发回调函数执行顺序无法确定。 异步编程 JavaScript中异步编程问题可以说是基础中的重点,也是比较难理解的地方。首先要弄懂的是什么叫异步? 我们的代码在执行的时候是从上到下按顺序执行,一段代码执行了之后才会执行下一段代码,这种方式...

    shadowbook 评论0 收藏0
  • ES6-7

    摘要:的翻译文档由的维护很多人说,阮老师已经有一本关于的书了入门,觉得看看这本书就足够了。前端的异步解决方案之和异步编程模式在前端开发过程中,显得越来越重要。为了让编程更美好,我们就需要引入来降低异步编程的复杂性。 JavaScript Promise 迷你书(中文版) 超详细介绍promise的gitbook,看完再不会promise...... 本书的目的是以目前还在制定中的ECMASc...

    mudiyouyou 评论0 收藏0
  • JS基础——同步异步的区别

    摘要:只要指定过这些事件的回调函数,这些事件发生时就会进入任务队列,等待主线程读取。异步任务必须指定回调函数,当主线程开始执行异步任务,就是执行对应的回调函数。 javascript语言是一门单线程的语言,不像java语言,类继承Thread再来个thread.start就可以开辟一个线程。所以,javascript就像一条流水线,仅仅是一条流水线而已,要么加工,要么包装,不能同时进行多个任...

    ztyzz 评论0 收藏0
  • JS异步那些事 一 (基础知识)

    摘要:异步那些事一基础知识异步那些事二分布式事件异步那些事三异步那些事四异步那些事五异步脚本加载事件概念异步回调首先了讲讲中两个方法和定义和用法方法用于在指定的毫秒数后调用函数或计算表达式。功能在事件循环的下一次循环中调用回调函数。 JS异步那些事 一 (基础知识)JS异步那些事 二 (分布式事件)JS异步那些事 三 (Promise)JS异步那些事 四(HTML 5 Web Workers...

    李涛 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<