一、引言
JavaScript 是单线程语言,一次默认只能做一件事。但我们在开发中经常遇到网络请求、定时器、文件读取等耗时操作------如果傻等它们完成,页面就会卡死。为了解决这个问题,JavaScript 引入了异步机制。本文将首先梳理同步与异步的区别、什么是线程与进程、回调函数的用法与产生原因、回调地狱,以及本文的重点:如何利用 Promise 优雅地解决这些问题。
二、同步与异步
- 同步执行------能按照我们写的代码顺序从上往下依次执行,执行完前边的再执行后边的
- 异步执行------在执行过程中,如果碰到一个耗时的任务,就分精力出来同时执行另一个任务,以高效率执行完整个代码
三、线程与进程
- 线程:CPU执行指令所需要的时间
- 进程:CPU接收到一个指令,到加载完上下文所需要的时间
四、首代方法------回调
启用V8来运行js这可以称之为一个进程,但是V8引擎默认只会开启一个线程来执行代码,所以遇到耗时的任务,V8会先将它挂起,先去执行不耗时的任务,这样就出现了一个严重的问题:
当不耗时的B函数依赖于耗时的A函数的执行结果时,V8会先执行不耗时的函数B
举个例子:
js
let a = null
function A() {
setTimeout(() => {
a = 100
}, 1000)
}
function B() {
console.log(a);
}
A() //函数A是个耗时函数,函数B是不耗时的,V8就会先把A挂起去执行B,
B() //但是B此时找不到值,因为a的赋值语句还未执行。
于是你便会想,把函数B的调用写在函数A的a=100后面,这样,我们就可以正常输出100了,这便是函数回调。
但是当程序繁重时,排查问题的难度大,可读性差,这就造成了所谓的回调地狱
五、最终的promise解决
那么为了解决回调地狱的问题,我们便引入promise函数。 一个 Promise 实例代表一个未来才会完成(或失败)的异步任务。它有三种状态:
- pending(进行中):初始状态,既不是成功也不是失败。
- fulfilled(已成功):操作成功完成。
- rejected(已失败):操作失败。
让我们从具体案例中来应用学习:
js
function date() {
setTimeout(() => {
console.log('约会成功');
}, 2000);
}
function marry() {
setTimeout(() => {
console.log('结婚成功');
}, 1000);
}
date() //这样有前文可知运行结果是: 结婚成功
marry() // 约会成功
那么我们会想:如果marry()的执行条件是date()执行完成,很显然就可解决此烦恼
那如果我们要这样做,我们就可以利用promise函数自带的状态属性:
date成功------>状态更改为成功------>执行marry
我们便可以这样写代码:
js
function date() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('约会成功');
resolve('yes')
}, 2000);
})
}
function marry() {
return new Promise((res) => {
setTimeout(() => {
console.log('结婚成功');
res()
}, 1000);
})
}
date().then(() => {
return marry()
})
-
return new Promise(...)date()被调用时,会立即返回一个 Promise 对象,初始状态为pending。 -
函数
(resolve, reject) => { ... }这个函数会同步立即执行(Promise 构造函数内部会马上调用它)。
-
回调函数内部:
resolve('yes'):将 Promise 状态从pending改为fulfilled,并且把字符串'yes'作为成功结果传递出去。
-
date()执行完毕后此时.then检测到状态被改变,执行marry()
六、总结
JavaScript 借助事件循环和任务队列,在单线程基础上实现了非阻塞的异步编程;而 Promise的出现,让异步代码的书写与维护越来越优雅。