JavaScript异步(一)异步概述

本文最后更新于:8 个月前

什么是异步?同步vs异步、同步&回调,是什么关系?异步操作有哪些特征?
JavaScript实现异步操作的3种方式

JavaScript异步(一)异步概述

一、什么是异步?

1、同步 vs 异步

同步:后面的代码必须等到前面的代码执行完,才能执行。

异步:后面的代码不必等到前面代码执行完,就可以执行。

2、异步函数提供的能力

  • 通过调用一个函数来启动一个长期运行的操作
  • 让函数开始操作并立即返回,这样我们的程序可以保持对其他事件做出反应的能力
  • 当操作最终完成时,通知操作的结果

3、两个形象的例子

举一个形象的例子,来区分同步和异步操作:

同步操作:在售票窗口买票,任务是买到票,只有排队买到了票,才能离开。

异步操作:在网红餐厅吃饭,任务是吃到饭,如果排队的人很多,可以先取号,在此期间你可以去逛街或者离开办其它事情,到号的时候餐厅会通知你,这个时候回去吃饭即可。

从餐厅吃饭的例子中,可以对应上异步函数的特征。首先是取号,启动了一个需要长时间等待的操作,同时立即返回一个号码,你可以抽出身完成其它事情;当饭要做好的时候,发出通知。对应了异步函数提供的三个能力。

4、异步 & 回调

4.1 关联

异步任务需要在得到结果时通知JavaScript来读取结果。等待通知的过程就是「异步」通知JavaScrpt读取结果都过程就是「回调」。具体的实现就是在异步任务启动时,指定回调函数(直接定义函数、或者提供函数引用)

4.2 区别

异步函数需要回调函数来通知结果。

但是回调函数不一定存在于异步任务中。同步任务中同样可以用到回调函数。

5、参考文章

异步 JavaScript - 学习 Web 开发 | MDN (mozilla.org)

异步与回调 - 知乎 (zhihu.com)

二、实现异步操作的方式

1、回调Callback

回调函数是异步操作最基本的方法。是曾经JavaScript主流的异步编程实现方式。但不限于用在异步操作上,同步操作中也可以使用回调。

回调函数,就是一个被传递到另外一个函数中、在适当时候被调用的函数。即自己写了不调用、给别人调用的函数。

1.1 以往的异步编程模式

假设异步操作会返回一个我们需要的值,那么需要想办法把这个值传递到需要它的地方。以往广泛的做法是「传入式回调」,即给异步函数提供一个回调,回调中包含要用到异步返回值的代码,作为回调的参数。注意:回调需要包括「成功回调」和「失败回调」

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function double(value,success,failure){
setTimeout(()=>{
try{
if(typeof value !== 'number'){
throw 'must provide number'
}
success(value*2);
}catch(e){
failure(e);
}
},1000);
}
const successCallback = (x)=>console.log(`Success:${x}`);
const failureCallback = (x)=>console.log(`Fail:${x}`);
double(3,successCallback,failureCallback); //等待一会后,输出:Success:6

这种方式已经不可取了(不是说不能用),而是因为它必须在初始化异步操作的时候就定义回调。异步函数的返回值只能在短时间内存在,只有在这短时间内将存在的值作为回调函数的参数才能成功接收这个值。

为了解决”必须在初始化异步操作时就定义回调“的问题,Promise出现了,后续文章将对其详细介绍。

1.2 实例

在同步任务中使用回调函数

1
2
3
4
5
6
7
8
9
10
11
12
function doHomework(subject,callback){
console.log(`do ${subject} homework`)
callback()
};

function finishHomework(){
console.log('finish homework');
};

doHomework('math',finishHomework);
//do math homework
//finish homework
1
2
3
var arr = [1,2,3]
arr.forEach(x=>console.log(x)) //1 2 3
//这里的x=>console.log(x),是回调函数

在异步任务中使用回调函数

1
2
3
4
5
6
7
8
9
function delayResolve(str){
return new Promise((resolve,reject)=>{
console.log(str);
setTimeout(resolve,3000); //setTimeout()任务并不会立即执行,而是被加入CallbackQueue,所以可以视为异步操作
});
}
delayResolve('p1 executor')
.then(()=>{delayResolve('p2 executor')})
.then(()=>{delayResolve('p3 executor')})

1.3 缺陷

  • 难以进行错误处理
  • 容易出现「回调地狱」

1.4 “回调地狱”

举个例子介绍什么是“回调地狱”

场景实例:先后读取两个文本,并按顺序展示文本,使用Callback的方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var fs  = require('fs');
fs.readFile(
'a-text-file.txt', //读取第一个文本
'utf8',
function(err,text) { //读取完成,回调
if (err) {
console.error(err);
} else {
console.log('First text file:',text); //显示第一段文本
fs.readFile(
'another-text-file.txt', //读取第二个文本
'utf8',
function(err,text) { //读取完成,回调
if (err) {
console.error(err);
} else {
console.log('Second text file:',text); //显示第二段文本
}
}
);
}
}
);

可以看到,使用Callback的方式,会让代码的嵌套层数过多。仅仅是一个非常简单的顺序读取操作,就已经让代码的可读性变得很差。而且串行加载,时间效率低。

2、发布-订阅模式

另一种实现异步的思路是采用「信号」驱动的模式。任务的执行不取决于代码的顺序,而取决于某个「信号」是否发出。存在一个「信号中心」,某个任务完成就向「信号中心」”发布“一个「信号」。其它任务可以”订阅“这个信号,从而知道自己什么时候开始执行。这就叫做「发布-订阅模式」。

其实DOM的「事件监听」,就是一种发布-订阅模式。

3、Promise

Promise是目前实现异步编程的主要方法。更多介绍:《JS异步(二)Promise》

4、参考文章

Javascript异步编程的4种方法 - 阮一峰的网络日志 (ruanyifeng.com)


JavaScript异步(一)异步概述
http://timegogo.top/2023/02/07/JavaScript/JavaScript异步(一)异步概述/
作者
丘智聪
发布于
2023年2月7日
更新于
2023年7月16日
许可协议