前言
我们先来说说什么是异步,异步操作并不仅仅局限于编程和计算机科学领域,我们在日常生活中也经常会遇到异步的情况。我们用这几个有趣的例子来帮大家理解一下:
- 电子邮件通信: 发送电子邮件是一种异步操作。你发送了一封邮件之后,并不需要立即等待对方回复,而是可以继续进行其他活动。当对方准备好回复时,他们会发送回复。
- 邮寄信件: 将信件寄出后,并不会立即送达,而是需要一些时间。寄件人不需要等待信件到达目的地,而可以进行其他活动。
- 预约和会议: 安排预约或会议也是一种异步操作。当你预约或安排会议时,并不需要等待对方立即确认,而是可以进行其他安排。对方会在适当的时候回复或确认。
- 交通信号灯: 交通信号灯的变化是异步的。当你等待红灯变绿时,你并不需要一直停车等待,而可以进行其他事情,只有当信号变绿时才需要继续行驶。
- 购物: 在网上购物时,提交订单后并不需要立即等待商品送达。你可以继续进行其他活动,而在适当的时间收到商品。
异步
异步(Asynchronous)是指在不同步或不同时的时间执行任务或操作的方式。在编程和计算机科学领域中,异步通常用来描述一种处理事件和任务的机制,其中不需要等待一个操作完成,而是可以同时执行其他操作。
在同步编程中,程序的执行是按照顺序一步一步进行的,每个操作都需要等待上一个操作完成后才能执行。这可能导致程序在等待某些操作完成的同时处于空闲状态。
相比之下,在异步编程中,程序可以继续执行其他操作而不必等待某个操作完成。当异步操作完成时,通常通过回调函数、Promise对象或异步/等待(async/await)等机制来处理结果。这种方式有助于提高程序的响应性和效率,特别是在处理网络请求、文件操作或其他涉及等待时间的任务时。
JavaScript(JS)是一种异步编程语言。JavaScript最初设计为一种用于在浏览器中操作DOM(文档对象模型)的脚本语言,因此异步编程对于处理用户界面的交互、网络请求和其他事件至关重要。
我们先来看一个异步的例子:
假如我们去饭店吃饭时,我们点好了菜之后,厨师开始炒菜,那我们在厨师炒菜的时候一般会玩玩手机,刷刷抖音,而不是坐在那里一动不动等着厨师上菜
function a (){ setTimeout(function(){ console.log('刷会抖音') },1000) } a() function b (){ console.log("菜做完啦!") } b()
我们先来看看输出结果:
我们可以看到输出顺序为菜做完啦
过了一秒钟之后再打印刷会抖音
。
这段代码涉及到异步执行的概念,主要是通过setTimeout
函数创建了一个定时器,该定时器会在指定的时间间隔后执行回调函数。让我们一步一步解释这段代码:
function a (){ setTimeout(function(){ console.log('刷会抖音'); }, 1000); }
这是一个函数 a
,其中使用 setTimeout
函数创建了一个定时器。这个定时器会在 1000 毫秒(即1秒)后执行一个匿名函数,而这个匿名函数的内容是输出 '刷会抖音'
。
然后,你调用了函数 a
:
a();
由于 setTimeout
是异步的,它会在后台计时,不会阻塞程序的执行。所以,调用 a
后,程序会立即继续往下执行,而不会等待定时器的回调执行。
接着,你有另一个函数 b
:
function b (){ console.log("菜做完啦!"); }
这个函数 b
用于输出信息 "菜做完啦!"
。
最后,你调用了函数 b
:
b();
在整个程序执行过程中,b
函数会立即执行,而不会等待定时器的���调函数。因此,你可能会在控制台看到类似于以下的输出:
菜做完啦! 刷会抖音
这是因为 b
函数会立即执行,而 a
函数中的定时器回调函数会在1秒后执行。这展示了异步执行的特性,使得程序能够继续执行而不必等待定时器完成。
那如果我们想要刷会抖音
在菜做完啦
之前执行,可以做些什么呢?
回调函数
function a(cb){ setTimeout(() => { console.log("刷会抖音"); cb() },1000) } function b(){ setTimeout(() => { console.log("菜做完啦"); }, 0); } a(b)
这段代码包含两个函数 a
和 b
,并通过将函数 b
作为回调函数传递给函数 a
来展示异步操作和回调函数的概念。让我们一步一步解释:
function a(cb) { setTimeout(() => { console.log("刷会抖音"); cb(); }, 1000); }
这是一个函数 a
,它接受一个回调函数 cb
作为参数。在函数体内,使用 setTimeout
函数创建了一个定时器,它会在 1000 毫秒(即1秒)后执行一个匿名函数。这个匿名函数的内容包括输出 '刷会抖音'
和调用回调函数 cb
。
然后,你有另一个函数 b
:
function b() { setTimeout(() => { console.log("菜做完啦"); }, 0); }
这是另一个函数 b
,它使用 setTimeout
创建了一个定时器,该定时器在 0 毫秒后执行一个匿名函数,输出 '菜做完啦'
。
最后,我们调用了函数 a
,将函数 b
作为回调函数传递进去:
a(b);
在这里,调用 a
会启动一个定时器,在 1 秒后执行 '刷会抖音'
并调用回调函数 cb
,而 cb
此时指向函数 b
。所以,即便函数 b
的定时器设置为0秒,它的执行会在函数 a
的定时器之后,但在整体执行上并没有造成延迟。
因此,我们会在控制台看到以下的输出:
刷会抖音 菜做完啦
这展示了如何通过回调函数来管理异步操作的执行顺序。
回调地狱
回调地狱(Callback Hell)是指在异步编程中,多个嵌套的回调函数形成的代码结构复杂、难以理解和维护的情况。这通常发生在处理多个异步操作,其中一个操作的结果需要作为参数传递给另一个操作,依此类推。
以下是一个简单的例子,展示了回调地狱的情况:
getDataFromServer(function (data) { process1(data, function (result1) { process2(result1, function (result2) { process3(result2, function (result3) { // More nested callbacks... }); }); }); });
在这个例子中,每个异步操作的结果都需要传递给下一个操作,导致代码缩进层次加深,可读性变差。这种结构会使代码难以维护,容易出现错误,并且不符合良好的代码风格。
在下一篇文章中我们会讲如何用更好的方式去处理异步