后端小白的Promise学习笔记
Promise
写在前面
本博文仅作为个人学习过程的记录,可能存在诸多错误,希望各位看官不吝赐教,支持错误所在,帮助小白成长!
一、认识Promise
Promise是什么?!
Promise是ES6提出的异步编程的一种新规范。
旧版本中完成异步任务都是通过简单的回调任务!
Promise解决什么问题?
由于JavaScript作为脚本语言,解释执行的特点所有的代码执行都是单线程,就导致有些操作需要借助异步操作来完成。而异步的任务的执行顺序难以控制。
例如下面的代码:(JavaScript中使用Node的内建模块fs操作文件就是异步任务):
const fs = require('fs')
fs.readFile('./1.txt', 'utf-8', (err, data) => {
if (err) {
throw err
}
console.log(data)
})
fs.readFile('./2.txt', 'utf-8', (err, data) => {
if (err) {
throw err
}
console.log(data)
})
fs.readFile('./3.txt', 'utf-8', (err, data) => {
if (err) {
throw err
}
console.log(data)
})
fs.readFile('./4.txt', 'utf-8', (err, data) => {
if (err) {
throw err
}
console.log(data)
})
代码并不会按照你所写代码的顺序执行,而是通过异步回调的方式执行,所以你就可能看到这样的结果:

如果我们想要读取的顺序受我们控制,我们就只能在回调函数中嵌套另一个异步任务,就像这样:
const fs = require('fs')
fs.readFile('./1.txt', 'utf-8', (err, data) => {
if (err) {
throw err
}
console.log(data)
fs.readFile('./2.txt', 'utf-8', (err, data) => {
if (err) {
throw err
}
console.log(data)
fs.readFile('./3.txt', 'utf-8', (err, data) => {
if (err) {
throw err
}
console.log(data)
fs.readFile('./4.txt', 'utf-8', (err, data) => {
if (err) {
throw err
}
console.log(data)
})
})
})
})
这样写的代码,输出的顺序永远是1,2,3,4,因为只有上一个文件的内容输出完成后才会启动下一个异步任务!
但是这个代码…,是不是看的头疼?!这就是**回调地狱!(callback hell)**有两个明显的缺陷:
- 难以阅读!
- 容易出错,且难以排查!
Promise的优势
- 允许链式调用,解决回调地狱问题
- 逻辑清晰,便于理解
二、Promise初上手
2.1、基本案例
我们先用一个最简单的例子,来演示一下Promise的使用:
就以上面读取文件为例:
首先我们创建一个Promise对象:

哇擦,这个参数你一看,头皮发麻。
Promise的构造函数,需要一个函数executer
(也即你的异步任务),而这个异步任务函数的参数是所两个函数,函数名分别为resolve
,reject
(这是我推荐的一种命名规范!)
resolve函数:函数类型是(value) => void
,先说一下,这个函数可以在你异步任务中调用,会将你的异步任务执行状态从待定(pending)转换为(fulfilled)成功!同时这个函数可以传入你异步任务的执行结果作为函数参数value
!
reject函数:函数类型的(reason) => void
,与resolve函数相反,它会将你的异步任务执行状态从待定(pending)转为(rejected)失败!并且可以传入一个值作为你异步任务的出错的原因,然后在具体的reject函数实现中进行处理!
const fs = require('fs')
const promise = new Promise((resolve, reject) => {
fs.readFile('./1.txt', 'utf-8', (err, data) => {
if (err) {
// 调用reject, 标识任务失败,同时传入err作为失败的reason
reject(err)
} else {
// 调用resolve, 标识任务成功,传入data作为异步任务的执行结果
resolve(data)
}
})
})
现在我们要知道几点:
- Promise的状态装换有两种:
pending -> fulfilled
与pending -> rejected
。 resolve
、reject
是两个回调函数,分别会在调用时将你的异步任务状态修改为fulfilled、rejected。我们在异步任务中调用了它们,但是并没有为它们写具体的实现
目前我们的代码运行,没有任何动静
现在通过promise对象的then
方法,对两种任务状态下的回调函数进行实现:
then
方法的两个参数分别刚好与resolve、reject函数的类型相同:

(并且then方法的返回值还是一个Promise对象,这是方便链式调用的独特设计!)
promise.then(
// 任务转态为成功时,执行此函数, value为异步任务中调用resolve传入的执行结果
(value) => {
console.log(value)
},
// 任务状态为失败时,执行此函数,reason为你在异步任务中传入的失败原因reason
(reason) => {
console.log(reason)
},
)
然后就可以开开心心测试代码啦~~
2.2、让小屁孩都能懂的解释
看完代码,不禁我都有疑问,为什么可以这样写?!这是个什么思路?!对于Java代码写的多的我,无法理解为什么!!
直到我看到了这篇文章:我以为我很懂Promise,直到我开始实现Promise/A+规范 - 知乎 (zhihu.com)
那么我们就从这项技术的命名开始说起,毕竟它的名字就能代表它的绝大部分思想的源泉!Promise,直译就是承诺!我们简称它为画饼。
那么我们创建Promise实例的过程也就是我们画饼的过程,这个具体的饼也就是Promise创建时我们传入的异步任务!
我们画饼时就会强调后果:
- 如果画出来了,各位就工资几何翻倍,别墅靠海。这也就是我们在“饼”中写下的
resolve()
- 如果饼没了,各位就天天加班,月月低保。这就是“饼”中的
reject()
当然这都是在画饼,都还是虚构的,还没有真正实现,谁都不清楚“饼”的结果如何!即这个饼的状态还是pending状态,它有可能fulfilled,也有可能变成rejected!(这取决于这个“饼”内部的走向)
既然画了饼,那必然有人来接这个”饼“,如果饼实现了我就朝九晚五,房车不愁。如果饼没了打工仔我整日就以泪洗面。而这个接饼的过程就是我们常见的发布与订阅模式,而在Promise的概念中,订阅就是通过then
方法来实现,所有的订阅者需要做好两方面的心理准备!这两手心理准备的实现就是在then方法的两个参数函数中体现。
除了一个饼可以多个人同时订阅外,一个饼还可以一层层往下传,也就是后面会学习的链式调用!比如老板先画了一个大饼给经理,然后经理又给部门组长画了一个饼,然后组长又给组员画了一个饼。这就是promise.then(..).then(..).then(..)
的结果!
2.3、util.promisify
如果所有的异步任务,都需要我们进行手动包装为Promise版本,写起来还是比较麻烦的。于是Node中util模块中提供了一个函数promisify
:传入一个遵循常见的错误优先的回调风格的函数(即以 (err, value) => ...
回调作为最后一个参数),并返回一个返回 promise 的版本。
我们先来搞清楚什么叫遵循常见的错误优先的回调风格的函数
- 函数参数中有一个回调函数
- 回调函数是这个函数最后一个参数
- 回调函数的参数列表中错误优先,例如
(err, value) => { ... }
很显然,我们刚才使用的fs.readFile()
就满足这个条件!!
那现在我们利用这个工具,来将其进行封装:
const util = require('util')
const fs = require('fs')
// 封装返回一个Promise对象
const promiseReadFile = util.promisify(fs.readFile)
promiseReadFile('./1.txt', 'utf8').then(
(value) => {
console.log(value)
},
(reason) => {
console.log(reason)
},
)
三、Promise状态与对象值
在基础案例中,我们说过Promise对象的三个状态:
pending
resolved/fulfilled
rejected
我可不是胡编乱造,看代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
</body>
<script>
const p1 = new Promise((resolve, reject) => {
resolve()
})
console.log(p1)
const p2 = new Promise((resolve, reject) => {
reject()
})
console.log(p2)
const p3 = new Promise((resolve, reject) => {})
console.log(p3)
</script>
</html>

他们由Promise对象的属性PromiseState
保存,我们通过JavaScript代码是无法获取到的,是JavaScript引擎内部使用的属性!
此外我们还要说的就是Promise的对象值,我们之前说过我们在异步任务中调用resolve()
、reject()
函数是,都是可以传参的。而向这两个函数的传递的参数,将会被保存到Promise的另一个属性中:PromiseResult
,上述代码中我们都没有进行传参所以都是undefined!
修改后的代码:
const p1 = new Promise((resolve, reject) => {
resolve('OK!')
})
console.log(p1)
const p2 = new Promise((resolve, reject) => {
reject('ERROR!')
})
运行结果:

什么可以改变PromiseState和PromiseResult?!
只有resolve(),reject()函数可以修改PromiseState和PromiseResult。
(不过有种特殊的情况,若在异步任务中throw了错误,PromiseState将被置为rejected):
const p3 = new Promise((resolve, reject) => {
throw 'ERROR'
})
console.log(p3)
其效果与使用reject()
相同。
四、Promise API
4.1、Promise构造函数
我们使用Promise构造函数创建promise对象的过程,我们称其为画饼的过程。过程中饼的状态都是pending
而画饼的结果有可能出现两种情况:fulfilled
、rejected
。我们创建Promise对象的过程中,内部的任务是同步执行的!
即你创建Promise对象的时间,就取决于你内部任务执行的时间!!
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
let number = Math.floor(Math.random() * 100)
if (number < 50) {
resolve(number)
} else {
reject(number)
}
}, 3000)
})
alert('Hello!')
promise.then(
(value) => {
console.log('nice! ' + value)
},
(reason) => {
console.log('dammit! ' + reason)
},
)
你猜这段代码怎么执行?!

有点出乎意料,也就是我们创建Promise是异步的,并不影响我们普通代码的运行(不管那句alter放哪,它都会首先运行!!)而我们的发布者即我们创建的Promise,和其订阅者即使用了promise的then
方法的,他们之间的运行过程是按顺序执行的!
订阅者必须等待发布者发布对应状态后,才能做出对应的动作!
并且这个响应动作,是异步的!
const promise = new Promise((resolve, reject) => {
resolve(521)
})
promise.then(
(value) => {
console.log('nice! ' + value)
},
(reason) => {
console.log('dammit! ' + reason)
},
)
console.log('Hello')

Promise的构造函数参数executor
,此函数的函数体就是我们异步任务的主体内容,函数体中调用resolve()
或reject()
方法时,就表示异步任务完成,并且Promise的State置为fulfilled
或rejected
!此时“饼”的状态也就确定了!订阅者也就会按照饼的状态做出对应的响应!
4.2、订阅者动作:then、catch、finally
then
promise.then表示一个promise订阅者,方法有两个参数:
onfulfilled
onrejected
两个参数都是函数,函数参数分别为value、reason.
两个函数分别会在发布者所给“饼”的状态为fulfilled
、rejected
时被执行!(相当于订阅者的两个动作!)
函数参数就是发布者发布饼时所传值即**PromiseResult
**
then方法的两个参数都是可选的,如果作为订阅者只关心任务完成的状态,那么onrejected
可以写null,反之亦然!
catch
与then相似,但是它只有一个参数:onrejected
。
意思也就是它只能对发布者任务状态为rejected
时候做出响应。可以看做是then(null, (reason) => { .. })
的简写!
finally
与try-catch-finally
的中finally的意义一样,finally(f)
其效果与then(f, f)
效果相同,即不管任务的状态是什么,只要调用resolve()
、reject()
使得PromiseState被settled,就会做出响应!
要注意的是,因为finally无论状态如何都会调用处理函数,但是处理函数是没有参数的!所以我们可以将其作为一个状态信息传递者。
const promise = new Promise((resolve, reject) => {
resolve(521)
})
promise
.finally(() => console.log('Promise is ready!!'))
.then(
(value) => {
console.log('nice! ' + value)
},
(reason) => {
console.log('dammit! ' + reason)
},
)
4.3、Promise.all()
函数用于同时执行多个任务,并返回结果。
函数的参数接收一个promise实例数组,返回值为一个新的promise对象。
仅当promise数组中所有的promiseState都为fulfilled
时,返回值的promiseState为fulfilled
。只要有一个rejected
,那么最终结果的state也是rejected
.
当结果的PromiseState为fulfilled
时,返回的promise的PromiseResult是一个数组,数组中每个元素对应函数参数中promise的result。
但是如果PromiseState为rejected
时,只会将第一个reject的数据加入PromiseResult,也只会抛出第一个错(因为只要遇到一个rejected,就可以决定Promise.all的最终State也为rejected!!)
全通过:
const promises = [
new Promise((resolve, reject) => {
setTimeout(() => resolve(1), 3000)
}),
new Promise((resolve, reject) => {
setTimeout(() => resolve(2), 1000)
}),
new Promise((resolve, reject) => {
setTimeout(() => resolve(3), 2000)
}),
]
const result = Promise.all(promises)

一个或多个失败:
const promises = [
new Promise((resolve, reject) => {
setTimeout(() => resolve(1), 3000)
}),
new Promise((resolve, reject) => {
setTimeout(() => reject(2), 1000) // reject!
}),
new Promise((resolve, reject) => {
setTimeout(() => reject(3), 2000) // reject!
}),
]
const result = Promise.all(promises)

第一个reject有效,后面即使reject了,也不能再改变了,因为返回值的promiseState已经确定了!
all的参数数组元素并不一定都要是promise对象,还可以是常规值!!这些常规值,会被直接加入Result中,并且不会影响all返回值promise的State
const promises = [
new Promise((resolve, reject) => {
setTimeout(() => resolve(1), 3000)
}),
'Hello',
2021,
]
const result = Promise.all(promises)

4.4、Promise.allSettled
Promise.all在面对全部fulfilled时,可以返回所有Promise的Result数组,但是如果其中有一个rejected,就只能获取rejected的信息。而allSettled
,无论Promise的状态如何,只要State被Settled,都会将State和Result封装为一个对象放入一个数组并设置为返回值Promise对象的Result进行返回!我们就可以看到所有的Promise中任务的执行状态和结果了。
const promises = [
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1)
}, 2000)
}),
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(2)
}, 2000)
}),
new Promise((resolve, reject) => {
setTimeout(() => {
reject(3)
}, 2000)
}),
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(4)
}, 2000)
}),
]
const result = Promise.allSettled(promises)
console.log(result)

all()和allSettled(),当参数数组任意一个Promise的状态为pending时,返回值Promise对象的状态也为pending!!
4.5、Promise.race
race,竞赛。和它的名字一样,谁先跑到就算谁“赢”。传入一个Promise对象数组,可以视为多条赛道,那条赛道先执行完,对State进行修改,那么最终的返回值Promise对象的Result和State就固定了!
const promises = [
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1)
}, 4000)
}),
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(2)
}, 1000)
}),
new Promise((resolve, reject) => {
setTimeout(() => {
reject(3)
}, 3000)
}),
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(4)
}, 2000)
}),
]
const result = Promise.race(promises)
console.log(result)

四条赛道,第二条跑道抢先起跑,所以返回值Promise对象第一次Settled,就是被第二跑道。所以结果与第二个Promise的结果一致!
4.6、Promise.resolve()/reject()
在现代的代码中,很少需要使用 Promise.resolve
和 Promise.reject
方法,因为 async/await
语法使它们变得有些过时了。
Promise.resolve(value)
创建一个State为fulfilled
,Result为value
的Promise对象!
等同于:
const promise = new Promise((resolve, reject) => {resolve(value)})
适用于将一个值封装为Promise对象,便于后续使用链式调用进行处理。
Promise.reject(reason)
创建一个State为rejected
,Result为reason
的Promise对象。
五、Promise重要注意点
5.1、改变状态和指定回调函数谁先发生
答案是:都有可能。
改变转态是指在executor中,使用resolve
或者reject
函数改变PromiseState。
指定回调函数是指使用then
方法创建订阅,并指定对应转态的回调函数!(注意这里说的是指定!并不是执行!)
我们可以分为两个场景来解释:
场景一:
某手机厂商预进行产品发布会,于是众多爱好者提前订阅了发布会提醒,并且已经做好了买到与不买两种状态的动作,这个过程就相当于是指定回调函数!而此时发布会还没有进行,所以发布者的状态还没有发生变化。
这就是典型的指定回调函数先于状态改变。
当发布会发布以后,订阅者会马上根据发布者的状态变化,做出响应!
在Promise的实现中,只要我们的executor中改变状态是通过一个异步任务完成的,那么就会先执行then
指定回调函数。然后执行异步任务完成状态改变:
const promise = new Promise((resolve, reject) => {
// 异步任务,改变状态
setTimeout(() => {
resolve('Hello World!')
}, 2000)
})
promise.then(
(value) => {
// ...成功回调函数体
},
(reason) => {
// ...失败回调函数体
}
)
场景二:
还是发布会的例子,发布会开完了,讲的一塌糊涂,产品功能烂到了极点,是一个极其失败的产品发布会!改品牌的很多粉丝看到这条新闻失望至极,路人看到也秒转黑。
这是状态改变先于指定回调函数。
发布会的糟糕表现在前,没有提前关注发布会的的粉丝与路人没有心理准备,在看到发布会结果后做出对应的反应。
这在Promise的实现中,只需要将转态改变使用同步任务完成即可。
const promise = new Promise((resolve, reject) => {
if (Math.random * 100 > 50) {
resolve("OK")
} else {
reject("ERROR")
}
})
5.2、then方法返回值
我们在最开始介绍Promise的特点的时候,提到了链式调用,并且我们已经确定了then方法的返回值也是一个Promise对象!也就可以实现饼传饼。可是返回的Promise的State与Result是如何确定的?!
我们分别从三种情况来说
返回一个Promise对象
const promise = new Promise((resolve, reject) => {
resolve('Hello!')
})
let result = promise.then(
(value) => {
// 返回一个Promise对象
return new Promise((resolve, reject) => {
resolve('World')
})
},
(reason) => {},
)
// 延迟输出
setTimeout(() => {
console.log(result)
}, 2000)

then方法会直接将Promise对象返回!如果使用then链式执行,那么下个订阅者就是此Promise的订阅者!
返回一个常规值
let result = promise.then(
(value) => {
// 直接返回一个常规值
return 2021
},
(reason) => {},
)
这个常规值也会被包装为一个Promise对象,State为fulfilled,Result就是这个常规值本身!

throw异常
let result = promise.then(
(value) => {
throw 'Error'
},
(reason) => {},
)
throw的内容将会被设置为Promise对象的Result,State为rejected

5.3、串联任务
这里就是使用Promise来解决地狱回调的关键手段了!你可以先创建一个初始任务,然后通过then对后续任务进行串联。
就以最开始的文件读取任务为例:
const fs = require('fs')
const promise = new Promise((resolve, reject) => {
fs.readFile('../callback-hell/1.txt', 'utf8', (err, data) => {
if (err) {
reject(err)
} else {
resolve(data)
}
})
})
promise
.then((value) => {
console.log(value)
return new Promise((resolve, reject) => {
fs.readFile('../callback-hell/2.txt', 'utf8', (err, data) => {
if (err) {
reject(err)
} else {
resolve(data)
}
})
})
})
.then((value) => {
console.log(value)
return new Promise((resolve, reject) => {
fs.readFile('../callback-hell/3.txt', 'utf8', (err, data) => {
if (err) {
reject(err)
} else {
resolve(data)
}
})
})
})
.then((value) => {
console.log(value)
return new Promise((resolve, reject) => {
fs.readFile('../callback-hell/4.txt', 'utf8', (err, data) => {
if (err) {
reject(err)
} else {
resolve(data)
}
})
})
})
.then(console.log)
虽然比起之前的代码量更多了,但是其实大部分都是重复代码。这样代码看起来过程感更强烈,任务就像流水线一样在顺次进行。每一次then返回的promise对象的Result都会传递给后续的订阅者去处理。
思考:如果我在最后一行再加上一个then(console.log),你觉得最后一次输出的内容是什么呢?!
答案是:
undefined
,因为then方法的返回Promise的对象其中的Result是根据then方法的返回值来确定的,而console.log没有返回值,那么返回的Promise对象State为fulfilled,但是Result为undefined!!
5.4、异常穿透
上面的串联任务代码中间,我们并没有对异常错误的捕捉。如果每个then中间都还要对上一个任务的异常进行捕捉,并且如果动作都一样的,将产生更糟糕的代码冗余。其实我们可以使用.catch()
,并将其放在任务的最后,就可以捕获到这条任务链中在执行过程中出现的错误了!!
// ....
.then(console.log)
.catch(console.warn)
现在我们误将2.txt和3.txt的路径写错,看看会输出什么:

果然是可以捕获到错误的,但是也仅限第一个出现的错误!!
为什么正确的4.txt也没能执行呢!?
当我们在2.tx文件读取失败时,返回的是一个状态为reject的Promise对象,然而后面的订阅者都没有对rejected状态的回调函数,那么就只能将错误继续抛出,也就是说从第一个出错的位置开始,后学的的订阅者都会执行
onrejected
回调函数!!如何让4.txt能够执行呢?!(即使前面出错了!)
每个订阅者都加上对rejected状态的回调函数! 并且在rejected回调函数中再把自己的任务复述一遍
.then(
(value) => {
console.log(value)
return new Promise((resolve, reject) => {
fs.readFile('../callback-hell/3.txt', 'utf8', (err, data) => {
if (err) {
reject(err)
} else {
resolve(data)
}
})
})
},
(reason) => {
console.log('3: 失败啦~')
return new Promise((resolve, reject) => {
fs.readFile('../callback-hell/3.txt', 'utf8', (err, data) => {
if (err) {
reject(err)
} else {
resolve(data)
}
})
})
},
)
就像这样,无论什么前面任务什么情况,我们都可以正确处理,并且不会影响自己的任务执行!当然这样写异常穿透也就不存在了。
5.5、中断Promise链
前面看到了,当使用串联任务时,如果每个订阅者的回调设置得当,且开启自己的任务后,并不会使得Promise链在遇到错误时就直接中断(因为这样任务执行的状态会在其订阅中进行处理,然后并不会向下传递了),它会一直执行到最后一个then()。
那么我们使用什么办法可以使得Promise链在执行时,直接中断不继续向下执行呢?!方法只有一个!返回一个State为pending的Promise!
const promise = new Promise((resolve, reject) => {
resolve(1)
})
promise
.then(
(value) => {
console.log(value)
// 自己的任务
return Promise.resolve(2)
},
(reason) => {
console.warn(reason)
// 自己的任务
return Promise.resolve(2)
},
)
.then(
(value) => {
console.log(value)
// 自己的任务
return Promise.reject(3) // 这里使用reject模拟异常
},
(reason) => {
console.warn(reason)
// 自己的任务
return Promise.resolve(3)
},
)
.then(
(value) => {
console.log(value)
// 自己的任务
return Promise.resolve(4)
},
(reason) => {
console.warn(reason)
// 自己的任务
return Promise.resolve(4)
},
)
.then(console.log)
执行结果到异常位置并没有中断,并且一直执行到了最后:你用throw也不好使!

如果你期望遇到异常就中断任务链,就只能在异常位置返回一个转态为pending的Promise对象!
try {
throw 3 // 模拟异常
} catch (e) {
console.warn(e)
// 返回一个pending状态的Promise
return new Promise(() => {})
}
这样,由于此任务状态没有改变,其订阅者也就无法进行相应的动作,Promise链也就自此中断!

5.6、微任务队列
Promise的订阅处理程序,then
、catch
、finally
都是异步的!
于是我们会看到这样一幕:
const promise = Promise.resolve(200)
promise.then(console.log)
console.log('Script finished')
代码的执行结果:

也就是这些处理程序之外的代码会在所有处理程序之前执行!!
而发生这种情况的唯一解释,就是本节要接触的微任务队列,异步任务需要适当的管理。为此,ECMA 标准规定了一个内部队列 PromiseJobs
,通常被称为“微任务队列(microtask queue)”(ES8 术语)。
而我们的处理程序都属于异步任务,所以他们并不会直接执行,而是向加入到微任务队列中。如其规范中所说:
- 队列(queue)是先进先出的:首先进入队列的任务会首先运行。
- 只有在 JavaScript 引擎中没有其它任务在运行时,才开始执行任务队列中的任务。
JavaScript引擎会优先执行非队列之中任务的代码,最后按入队顺序顺次调用微任务队列中的异步任务!
如果你希望某些代码必须在处理程序之后执行,你可以使用then
将其转换为异步的,然后被加入到微任务队列中。
const promise = Promise.resolve(200)
promise
.then(console.log)
.then(() => {console.log('Script finished')})
六、async与await
Async/await 是以更舒适的方式使用 promise 的一种特殊语法,同时它也非常易于理解和使用。
6.1、async
用于修饰函数的关键字。使用async
修饰的函数有以下特点:
- 返回值是一个Promise对象
- 返回的Promise对象的State与函数的运行结果有关,如果抛出异常State为
rejected
,否则为fulfilled
- 返回的Promise的Result,也与函数的运行结果相关,如果结果是常规值将直接用于设置Result。
测试使用:
async function sayHi() {
return 'Hello World'
}
const promise = sayHi()

这样我们的异步任务就不要使用new Promise
来进行包裹了,直接使用async
来修饰我们异步任务函数即可。
async经常会与await
联合使用,来看看这个小家伙。
6.2、await
await
通常用于修饰一个表达式,表达式可以是Promise对象,也可以是常规值!
- 当是Promise对象时
- 若对象State为
fulfilled
时,返回PromiseResult! - 若对象State为
rejected
时,直接抛出异常,异常内容为PromiseResult
- 若对象State为
- 当为常规值时,直接返回
await只能在async修饰的函数中使用!!!
如果你敏锐度高,你可能已经闻到了一丝丝味道,两者的设计和使用都是这么巧妙~(await可以解析一个async函数的返回值)
我们先来测试使用一下await:
async function sayHi() {
return await 200
}
const promise = sayHi()

到这里,你看出什么端倪了吗?! 为什么说await和async是使用Promise的语法糖?!
6.3、结合使用
我们现在使用async和await来完成文件读取案例:
const fs = require('fs')
const util = require('util')
// 先将fs.readFile 转为Promise方法
const promiseReadFile = util.promisify(fs.readFile)
async function main() {
try {
// 将Promise化的文件读取方法的Result使用await进行转换
let file1Content = await promiseReadFile('../callback-hell/1.txt', 'utf8')
let file2Content = await promiseReadFile('../callback-hell/2.txt', 'utf8')
let file3Content = await promiseReadFile('../callback-hell/3.txt', 'utf8')
let file4Content = await promiseReadFile('../callback-hell/4.txt', 'utf8')
console.log(file1Content, file2Content, file3Content, file4Content)
} catch (e) {
console.warn('Error' + e)
}
}
main()
可以看到,我们在使用await和async时,是没有写任何回调函数的,就像写同步代码一样。await可以直接取到我们异步任务的执行结果值,我们拿来即用,代码十分清爽易懂。
当我们使用async和await时,我们基本不再需要使用then、catch,await
帮我们的处理了等待,代替了then中指定回调函数来进行promise任务结果处理。使用await我们可以拿到Promise任务结果然后进行同步处理,这就省去了我们在then中写的俩回调!
promise.then(
(value) => {
console.log(value)
// ...其他处理
}
)
// 等效于
let value = await promise
console.log(value)
// .. 其他处理
并且使用常规的try...catch()
而不是.catch
。
以上就是Promise的入门使用的全部笔记。
学习参考:
Promise,async/await (javascript.info)
async function main() {
try {
// 将Promise化的文件读取方法的Result使用await进行转换
let file1Content = await promiseReadFile(’…/callback-hell/1.txt’, ‘utf8’)
let file2Content = await promiseReadFile(’…/callback-hell/2.txt’, ‘utf8’)
let file3Content = await promiseReadFile(’…/callback-hell/3.txt’, ‘utf8’)
let file4Content = await promiseReadFile(’…/callback-hell/4.txt’, ‘utf8’)
console.log(file1Content, file2Content, file3Content, file4Content)
} catch (e) {
console.warn(‘Error’ + e)
}
}
main()
可以看到,我们在使用await和async时,是没有写任何回调函数的,就像写同步代码一样。await可以直接取到我们异步任务的执行结果值,我们拿来即用,代码十分清爽易懂。
当我们使用async和await时,我们基本不再需要使用then、catch,`await`帮我们的处理了等待,代替了then中指定回调函数来进行promise任务结果处理。使用await我们可以拿到Promise任务结果然后进行同步处理,这就省去了我们在then中写的俩回调!
```javascript
promise.then(
(value) => {
console.log(value)
// ...其他处理
}
)
// 等效于
let value = await promise
console.log(value)
// .. 其他处理
并且使用常规的try...catch()
而不是.catch
。
以上就是Promise的入门使用的全部笔记。
学习参考: