JS学习之异步

JavaScript是单线程的,很多任务都是堆到这个线程上的,然后一个一个的解决。

这就导致了一些问题,比如当前面有一个函数在做复杂运算时,没有将控制权返回浏览器,那么此时你无法在做其他的事,这叫阻塞。下面是官方给出的一个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<div class='wrapper'>
<button class='btn'>Click</button>
</div>
<script>
const wp = document.querySelector('.wrapper');
const btn = document.querySelector('.btn');
btn.addEventListener('click', () => {
let myDate;
for(let i = 0; i < 10000000; i++) {
let date = new Date();
myDate = date;
}
console.log(myDate);
let pElem = document.createElement('p');
pElem.textContent = 'This is a newly-added paragraph.';
wp.appendChild(pElem);
})
</script>
可以看到点击click后,先做复杂运算,过段时间才会显示段落p(这两个没有逻辑关系)

这表明JS是一种同步的、阻塞的、单线程的语言,一次只能执行一个操作。但web浏览器定义了函数和API,允许我们当某些事件发生时不是按照同步方式,而是异步地调用函数(比如,时间的推移,用户通过鼠标的交互,或者获取网络数据)

在网络上,异步更为重要,如下面这个例子

1
2
var response = fetch('myImage.png');
var blob = response.blob();

因为不知道第一步下载图片的时间,所以第二步有可能会出错。需要等第一步返回才能进行第二步

有两种解决方案,以前的回调callback,和现在的promise,重点学习promise

不过在此之前要学习几个函数方便对异步的理解,这些函数设置的异步代码实际上在主线程上运行,但是您可以在迭代之间运行其他代码,运行效率或高或低,这取决于这些操作的处理器密集程度。

异步函数

setTimeout()

可以指定时间后运行代码

  • 第一个参数是函数引用
  • 第二个参数是时间间隔,单位毫秒
  • 后面是可变参数,这些参数会传递给第一个参数的函数

返回一个标志,后续可根据这个标志使用clearTimeout取消这个任务,如

1
2
let taskFlag = setTime(console.log, 2000, 'I love you');
clearTimeout(taskFlag);

当时间间隔设置为0时,会在主线程执行后立刻执行

1
2
3
4
setTimeout(function() {
alert('World');
}, 0);
alert('Hello');// Hello,world

setInterval()

setTimeout的只执行一次不同,setInterval会循环执行,它的参数与setTimeout一致

一个数字时钟的例子

1
2
3
4
5
6
7
8
9
10
11
<div>
<p id='demo'></p>
</div>
<script>
function displayTime() {
let date = new Date();
let time = date.toLocaleTimeString();
document.getElementById('demo').textContent = time;
}
const createClock = setInterval(displayTime, 1000);//每秒执行一次
</script>

它同样会返回一个标志,使用clearInterval可以停止执行

下面是一个秒表例子

源码(最开始使用Date来计时,但是由于暂停逻辑有点麻烦,就采用一个变量记录秒方便一点)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<div>
<p id="clock"></p>
<button id='stop'>stop</button>
<button id='start'>start</button>
<button id='reset'>reset</button>
</div>
<script>
const stop = document.querySelector('#stop');
const start = document.querySelector('#start');
const reset = document.querySelector('#reset');

// sec记录按下start的秒数
let sec=0, s=0, m=0, h=0;
// 存储setInterval的标志
let stopFlag;

// 初始化
displayCount();

// 所有点击事件
start.onclick = () => {
stopFlag = setInterval(displayCount, 1000);
start.disabled = true;
};
stop.onclick = () => {
clearInterval(stopFlag);
start.disabled = false;
};
reset.onclick = () => {
clearInterval(stopFlag);
sec = 0;
start.disabled = false;
displayCount();
}

function displayCount() {
time();
if(s<10) s = '0'+s;
if(m<10) m = '0'+m;
if(h<10) h = '0'+h;
timeText = h+':'+m+':'+s;
document.getElementById('clock').textContent = timeText;
}
function time() {
// 计算,可以使用math辅助
if(sec<3600){
h = 0;
m = parseInt(sec / 60);
s = sec % 60;
} else {
h = sec % 3600;
m = parseInt(sec / 60) - h;
s = sec % 60;
}
sec++;
}
</script>

递归setTimeout()

递归的作用可以实现与setInterval一样的功能

1
2
3
4
5
6
7
let i = 0;
funtion run(){
cosole.log(i);
i++;
setTimeout(run,100);
}
setTimeout(run,100);

setInterval的区别

  • setTimeout的时间间隔是固定的
  • setInterval的事件间隔包括了程序运行的时间
  • 所以当代码花的时间比设置的间隔更长时建议使用递归

回调

这是一个比较老套的做法

比如addEventListener()的第二个参数就是异步,第一个参数监听事件,第二个参数就是当事件发生时调用的函数。第二个参数相当于只是传递了过去,并没有执行。

下面是官方的一个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function loadAsset(url, type, callback) {
let xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.responseType = type;

xhr.onload = function() {
callback(xhr.response);
};

xhr.send();
}

function displayImage(blob) {
let objectURL = URL.createObjectURL(blob);

let image = document.createElement('img');
image.src = objectURL;
document.body.appendChild(image);
}

loadAsset('https://cdn.jsdelivr.net/gh/yangchaohe/yangchaohe.github.io@static/img/blog/mito2.jpg', 'blob', displayImage);

当获取图片后对图片做一些处理

回调函数不一定都是异步的,如forEach

Promise

简介

现代异步编程的常用方案,比回调、事件更加合理、强大。这里面很多东西比较晦涩难懂,我尽量梳理清楚

为了方便理解Promise,需要理清几个概念

1.事件队列

像promise这样的异步操作被放入事件队列中,事件队列在主线程完成处理后运行,这样它们就不会阻止后续JavaScript代码的运行。排队操作将尽快完成,然后将结果返回到JavaScript环境。

2.回调和Promise

使用监听鼠标事件,当鼠标点击就会执行事件(函数),也就是说这个事件被阻塞了,只有给定条件发生才会执行;同样Promise也是将里面的代码阻塞,然后监听的是资源是否获取到了,进而执行代码.

3.promise里的状态

每个promise对象都存在一个状态,初始状态叫pending,成功状态fullfilled,失败状态rejected,当从初始状态转化成失败或者成功时,就无法再改变状态

参考文献

作者

manu

发布于

2020-05-02

更新于

2023-01-06

许可协议


:D 一言句子获取中...