什么是异步操作
- 在JavaScript中,异步行为通常是指那些不会立即完成的操作,比如网络请求、文件操作或任何需要等待的操作。为了管理这种异步行为,JavaScript 提供了几种机制,其中 Promise 是非常核心的一个。
异步操作的示例
- 如果你不知道什么是异步操作,你可以看这个例子:
function operate1() {
// http 发出请求;等待回复
....
console.log("operate1 finished")
}
function operate2() {
// http 发出请求;等待回复
....
console.log("operate2 finished")
}
operate1();
operate2();
- 上述描述了两个函数
operate1()
和operate2()
operate1()
和operate2()
中都定义了两个网络请求,而网络请求取决于网络情况、服务器那边处理的速度,所以会导致最终这两个函数的结果不知道
什么是 promise
Promise
是一个 代表了异步操作最终完成或失败的对象。它可以让你组织异步代码的方式更加清晰,避免所谓的“回调地狱”。这里面涉及了三个概念:- promise 是一个对象
- 这个对象代表了异步操作的 ”完成” 或者 “失败”
为什么要用 promise
-
一言以蔽之:为了将那些异步的程序,按照顺序地执行。
-
举个简单的例子,你今天要做以下的事情:
- 给 A 发送微信消息,
- 等 A 回复之后你要约 B 吃饭的时间。
- 等 B 告诉你具体时间之后你需要去洗车
- 洗碗
- 刷盘子
-
你给 A 发了消息之后他一直没回复。这时候如果你是用
同步
的方式在执行。那在 A 回消息之前(这个过程可能要两个小时),你无法执行后面的步骤;这时候用程序表示:// 同步操作的程序 执行一天的事情(){ 1. 给 A 发送微信消息(); 2. 约 B 吃饭的时间(); 3. 洗车(); 4. 洗碗(); 5. 洗盘子() }
-
如果是异步执行,那么 你把 A 的消息发送出去之后不必等到 A 的回复 你就可以进行后面的步骤
-
如果写成代码就是:
// 异步操作的程序 async 执行一天的事情(){ 1. 给 A 发送微信消息(); 2. 约 B 吃饭的时间(); 3. 洗车(); 4. 洗碗(); 5. 洗盘子() }
-
因为有了
async
这个关键词,使你的执行变成了异步
的方式;首先要注意一点:- 异步的意思 并不是说,现在 1,2,3,4, 5 可以同时执行了,那不叫
异步
,那叫并发
;并发和异步的区别在于异步
是单线程中对效率最大化的追求,而并发
则是有两个及以上的线程
或者进程
;同时异步
程序由于是单线程,所以程序还是从上到下依次执行,但是并发
定义范围内的程序是可以真正做到 “同时” 执行的。当然并发也分为真并发
和伪并发
一旦并发数量超过cpu
的核数,其实就是伪并发
而不是真并发
,当然这个不是我们这篇文章关注的内容。 - 异步的意思是 1,2,3,4, 5 还是会先执行 1, 然后 2, 然后 3,… 但区别是:
- 之前
同步
的时候,2 必须等 1 完全执行完成了,才能执行, - 但现在是只要
给 A 发送微信消息();
的消息发出去了,不必等到 A 回复就可以执行约 B 吃饭的时间()
这个操作;这样做的目的是不阻塞整个后面的程序执行。总结成一句话就是:异步是按照代码的顺序触发,但不等待每个函数的执行结果
- 之前
- 也就是说,一旦使用
async
来定义函数,函数内部就会以更高效率执行,虽然还是会按照 code 的顺序一行行触发,但每个单独的函数不再等待上一个完成。除非你用await
关键字,那么整个异步程序的焦点就会放到await
上,等待await
定义的那一行执行结束。async 执行一天的事情(){ 给 A 发送微信消息(); await 约 B 吃饭的时间(); 洗车(); 洗碗(); 洗盘子() }
- 但问题又来了。
await
只能保证程序运行到 B 这里能够停住,得到 B 的返回结果之后再往下进行,但是并不能保证 A, B 这两个程序之间的顺序。 - 为了保证多个异步程序之间需要 “按照顺序” 执行,就要引入
promise
;也就是说,如果我要保证必须按照给 A 发送微信并且得到 A 的回复之后才能跟 B 约吃饭时间
;并且我还不想阻塞其他的与此 “无关” 的行为执行,即,我想保证程序中部分的操作是 “有序的” 其他单独的操作效率最大化,那么这个时候就需要 promise 的操作(并不是说没有 promise 就无法实现,而是 promise 的引入让这个过程变的很简单)
- 异步的意思 并不是说,现在 1,2,3,4, 5 可以同时执行了,那不叫
promise 对象
一个 Promise
对象有三种状态:
-
Pending(进行中): 初始状态,既不是成功,也不是失败状态。
-
Fulfilled(已成功): 意味着操作成功完成。
-
Rejected(已失败): 意味着操作失败。
-
既然是个对象,在
javascript
中肯定就要用new
的方式来创建,所以,如果我们要创建一个promise
的话,一个标准的方式是:let promise = new Promise(function(resolve, reject) { // 这里是异步执行的部分,可以是任何异步的操作 setTimeout(() => { // 成功执行后调用 resolve() resolve("操作成功"); // 如果有错误或者不想执行成功,调用 reject() // reject("发生错误"); }, 1000); });
-
这里你肯定有个问题就是:
resolve
和reject
是什么? -
简单来说
resolve
是 promise 中封装的代码逻辑成功运行完成的标志
;而reject
则是 promise 中的函数逻辑运行失败的标志
-
而如果 promise 里面定义的程序还在运行的过程中,promise 对象的状态就是
pending
-
在上面的程序中,当睡眠 1000 ms 之后,会自动触发 resolve 标志。这个 resolve 标志的作用主要是我们在构建
promise 链
时,告诉后面的异步程序 “我这边执行完了,拿到结果了,你可以继续运行了”
promise 链
- 假设我们对
给 A 发送微信消息();
和约 B 吃饭的时间();
这两个 “耗时很长” 的步骤用 promise 定义一下,大概长下面的样子
function 给 A 发送微信消息() {
return new Promise(resolve => {
response = http.post(....)
resolve(response)
});
}
function 约 B 吃饭的时间() {
return new Promise(resolve => {
response = http.post(....)
resolve(response)
});
}
-
由于他们两个的核心逻辑都用 promise 进行封装了,因此我们可以用 promise 的
then
语法来构建 promise 链保证他们可以顺序执行。async 执行一天的事情(){ 给 A 发送微信消息().then(()=>{ 约 B 吃饭的时间() } ) 洗车(); 洗碗(); 洗盘子() }
-
这样就能保证
约 B 吃饭的时间()
一定要发生在 A 这个函数完全完成之后; -
但同时,其他的程序还是能够正常执行(不阻塞)