Promise的使用与实现
[TOC]
引言:
Promise是ES6引入进行异步编程的新的解决方案,从语法上来说,就是一个构造函数,可以封装异步的任务,并且可以对结果进行处理。
Promise最大的好处在于可以解决回调地狱的问题,并且在指定回调和错误处理方面,更加的灵活与方便。
课程大纲
- 1.
Promise介绍和基本使用 - 2.
Promise API - 3.
Promise关键问题 - 4.
Promise自定义封装 - 5.
async与await
0.前置知识
0.1.区别实例对象和函数对象
实例对象
new函数产生的对象,称为实例对象, 简称对象
函数对象
- 将函数作为对象使用时,简称函数对象(都是
Object函数的实例对象)
- 将函数作为对象使用时,简称函数对象(都是
先看懂语法,再看懂功能
- 每个变量是什么数据类型
- 返回的是什么数据类型
- 括号的左边必然是函数,点的左边必然是对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
function Fn() { // Fn是函数
}
const fn = new Fn() // Fn是构造函数,fn是实例对象(简称对象)
console.log(Fn.prototype) // Fn是函数对象
Fn.bind({}) // Fn是函数对象
$('#test') // JQuery函数
$.get('/test') // JQuery函数对象
</script>
</body>
</html>
0.2.两种类型的回调
同步回调
立即执行,完全执行完了才结束,不会放入回调队列中
例子:
数组遍历相关的回调函数
1
2
3
4
5const arr = [1, 3, 5]
arr.forEach(item => {
console.log(item) // 遍历的回调函数,是同步回调,不会放入队列,一上来就会执行
})
console.log('forEach()之后')
Promise的executor函数
异步回调
不会立即执行,会放入回调队列中将来执行
例子
1
2
3
4setTimeout(() => {
console.log('timeout callback()') // 异步回调函数,会放入队列中将来执行
})
console.log('setTimeout()之后')
如何判断是异步还是同步的呢?
- 打印看一下输出顺序
拓展:10张图让你彻底理解回调函数 - 知乎 (zhihu.com)
0.3.JS中error的处理
Error - JavaScript | MDN (mozilla.org)
错误类型
Error:所有错误的父类型ReferenceError:引用的变量不存在1
2console.log(a) // 如果没有捕获异常,下面的代码是不会执行的
console.log('-----')
TypeError:数据类型不正确的错误1
2
3
4let b = null
console.log(b.xxx) // 想要读属性,点的左边应该是一个对象
b.xxx() // 也是类型错误
RangeError:数据值不在其允许的范围内1
2
3
4
5
6function fn() {
fn()
}
fn()
SyntaxError:语法错误1
const c = """"

错误处理(如果不处理,程序就无法向下执行)
捕获错误:
try...catch1
2
3
4
5
6
7
8
9try {
let d
console.log(d.xxx)
} catch(error) {
console.log(error)
console.log(error.message)
console.log(error.stack)
}
console.log('出错之后')
打断点查看
error对象
抛出错误:
throw error1
2
3
4
5
6
7
8
9
10
11
12
13function doSomething() {
if(Date.now() % 2 === 1) {
console.log('当前日期为奇数,可以执行任务')
} else {
throw new Error('当前日期为偶数,无法执行')
}
}
try {
doSomething()
} catch (error) {
alert(error.message)
}
错误对象
message属性:错误相关信息stack属性:函数调用记录栈记录信息
1.Promise初体验
index.html
1 |
|
1.1.Promise封装异步操作
1 |
|
如下图框选的[[PromiseState]],表示的就是Promise对象p的状态,该状态会传递给then方法,不同的状态会传递给then方法不同的形参

1.2.获取成功/失败的结果值
新需求:打印输出中奖的数字
Promise对象用来封装一个异步操作,并可以获取其成功/失败的结果值
在实例化Promise时,封装的异步操作里面,任何定义的变量,都可以当作时结果值,传递给resolve和reject
我们可以将n看作是结果值,传递给resolve和reject
1 | if(n <= 30) { // 将 n <= 30 认为是成功的状态 |
对象p可以拿到处理成功/失败的结果,作为参数传递给then方法对应的成功/失败的回调函数,默认then方法两个回调函数的参数分别是value和reason,写成a和b也行
1 | p.then((value) => { |
如下图框选的[[PromiseResult]],表示的就是Promise对象p获取到异步操作的结果值,该结果值根据状态的不同,传递给then方法不同的形参

1.3.Promise练习
1.3.1.fs读取文件
回调函数形式
1 | const fs = require('fs') |
Promise形式
1 | const fs = require('fs') |
1.3.2.AJAX请求
原生AJAX请求
1 |
|
Promise形式
1 |
|
1.4.Promise封装
1.4.1.Promise封装fs读取文件操作
1 | // 封装一个函数myReadFile,读取文件内容 |
当这样封装后,不需要在readFile后写回调函数,可以在then方法里写回调函数
1.4.2.使用util.promisify方法进行promise风格转化
上一小节中,我们是手动封装了require('fs').readFile方法
node中可以使用util.prmisify来达到自动封装的效果
官网:util 实用工具 | Node.js API 文档 (nodejs.cn)
util.promisify(original)新增于: v8.0.0
original<Function>- 返回:
<Function>采用遵循常见的错误优先的回调风格的函数(也就是将
(err, value) => ...回调作为最后一个参数),并返回一个返回 promise 的版本。
1 | // 引入util模块 |
作用:我们不需要每个函数都手动封装成Promise风格的函数
1.4.3.Promise封装AJAX
1 |
|
2.Promise的理解和使用
2.1.Promise是什么?
2.1.1.理解
抽象表达:
Promise是一门新的技术(ES6规范)Promise是JS中进行异步编程的新解决方案旧解决方案是是使用回调函数
- 什么是回调函数:10张图让你彻底理解回调函数 - 知乎 (zhihu.com)
异步操作包括哪些呢
fs文件操作1
require('fs').readFile('./index.html',(err,data) => {})
数据库操作
网络请求(AJAX)
1
$.get('/server', (data) => {})
定时器(setTImeout)
1
setTimeout(() => {},2000)
具体表达:
从
语法上来说,Promise是一个构造函数,可以通过new来实例化实例化时需要一个
函数类型的值作为参数,并且这个函数有两个形参resolve、reject(写成a和b也可以)1
2
3const p = new Promise((resolve, reject) => {
// 包裹异步操作
})resolve和reject都是函数类型的数据- 当
异步任务成功时,调用resolve函数,并将Promise对象p的状态,设置为成功 - 当
异步任务失败时,调用reject函数,并将Promise对象p的状态,设置为失败 - 异步任务是成功还是失败,取决于实际业务逻辑,具体可见
2.3.1的案例
- 当
p.then()Promise的实例对象有一个then方法,这个方法执行时接收两个函数类型的参数第一个函数,是对象
p状态为成功的回调函数第二个函数,是对象
p状态为失败的回调函数1
2
3
4
5p.then(() => {
// 成功状态后的操作
}, () => {
// 失败状态后的操作
})
从功能上来说,
Promise对象用来封装一个异步操作,并可以获取其成功/失败的结果值- 详见
2.3.2的案例
- 详见
2.1.2.Promise对象的状态改变
状态是Promise实例对象的一个属性:[[PromiseState]]
该属性的可能值
pending:初始化的默认值,表示未决定resolved/fulfilled:表示(异步操作的结果)成功rejected:表示(异步操作的结果)失败
状态的改变
- 异步操作
成功,Promise对象状态,由pending变成resolved - 异步操作
失败,Promise对象状态,由pending变成rejected - 说明:
Promise对象状态只有这两种,且一个Promise对象的状态,只能改变一次- 无论
Promise对象状态变为成功还是失败,都会获取到异步操作的结果数据(前提是给resovle和reject函数传参了,否则结果数据为undefined) 成功状态获取到结果数据一般称为value,失败状态获取到的结果数据一般称为reason
- 异步操作
2.1.3.Promise对象的结果值
结果值是Promise实例对象的一个属性:[[PromiseResult]],保存着Promise对象成功/失败的结果值
谁可以修改这个
结果值呢?resolve/reject这两个函数,是可以对实例对象中的结果值进行修改的
在后续
then方法的回调函数中,就可以把实例对象的结果值取出来,然后进行相关的操作
2.1.4.Promise执行的基本流程

2.2.为什么要使用Promise?
2.2.1.指定回调函数的方式更加灵活
- 旧的:
- 必须在异步任务前指定
Promise:- 启动异步任务 => 返回
Promise对象 => 给Promise对象绑定回调函数(甚至可以在异步任务结束后指定/多个)
- 启动异步任务 => 返回
2.2.2.支持链式调用,可以解决回调地狱的问题
什么是回调地狱?
回调函数的嵌套调用,外部回调函数异步执行的结果,是嵌套的回调执行的条件
1
2
3
4
5
6
7
8
9asyncFunc1(opt, (...args1) => {
asyncFunc2(opt, (...args2) => {
asyncFunc3(opt, (...args3) => {
asyncFunc4(opt, (...args4) => {
// some operation
})
})
})
})
回调地狱的缺点
- 语法上的一层层嵌套,只是最基础的
- 不便于阅读
- 不便于异常处理
- 可能会写很多重复性代码
- 控制反转问题(重点)
解决方案
2.3.如何使用Promise
2.3.1.API
Promise构造函数
Promise构造函数:Promise(executor) {}Promise构造函数实例化时,需要接收一个函数类型的参数,可以是箭头函数或匿名函数,该参数被称为executor函数executor函数:表示执行器函数,就是(resolve, reject) => {}执行器函数特点:会在
Promise内部立即同步调用,异步操作在执行器函数执行1
2
3
4
5const p = new Promise((resolve, reject) =>{
console.log('aa')
})
console.log('bb')打印结果如下:

先输出
aa,再输出bb,就说明执行器函数内部的代码,和第5行的代码,是同步调用的,也就是说,执行器函数的代码,不会进入到队列中,会立即执行(后面进行封装时要注意这一点)
resolve函数:内部定义成功时,我们调用的函数,并且会修改Promise对象的结果值:value => {}reject函数:内部定义失败时,我们调用的函数。并且会修改Promise对象的结果值:reason => {}
Promise.prototype.then方法
Promise.prototype.then方法:(onResolved, onRejected) =>{}then方法有两个参数,也都是函数类型的参数,指定用于得到成功value的成功回调,和用于得到失败reason失败的回调,返回一个新的Promise对象onResolved函数:成功的回调函数,并获取到Promise对象的结果值:value => {}onRejected函数:失败的回调函数,并获取到Promise对象的结果值:reason => {}
then方法的返回值:为一个新的Promise对象如果
then方法的回调函数中,抛出异常,新Promise状态变为rejected,结果值reason为抛出的异常1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16let p = new Promise((resolve, reject) => {
resolve('ok')
})
// 执行then方法
let result = p.then(value => {
throw 'Error'
}, reason => {
console.log(reason)
})
console.log(result)
result.catch(reason => {
return reason
})
如果
then方法的回调函数中,返回的是非Promise的任意值,新Promise状态变为resolved/fulfilled,结果值value为返回的值1
2
3
4
5
6
7
8
9
10
11
12
13let p = new Promise((resolve, reject) => {
resolve('ok')
})
// 执行then方法
let result = p.then(value => {
return 521
}, reason => {
console.log('reason为:' + reason)
})
console.log(result)
如果
then方法的回调函数中,返回的是另一个新的Promise,此Promise的结果就会成为新的Promise的结果1
2
3
4
5
6
7
8
9
10
11
12
13
14
15let p = new Promise((resolve, reject) => {
resolve('ok')
})
// 执行then方法
let result = p.then(value => {
return new Promise((resolve, reject) => {
resolve('success')
})
}, reason => {
console.log('reason为:' + reason)
})
console.log(result)
如果
then方法的回调函数中,即没有抛出异常,也没有return,则新的Promise对象的结果值为undefined
Promise.prototype.catch方法
Promise.prototype.catch方法:(onRejected) => {}catch方法也是用来指定回调的,不过只能用来指定失败的回调catch只是做了一个单独的封装,内部也是用then方法来实现的
onRejected函数:失败的回调函数,并获取到Promise对象的结果值:reason => {}1
2
3
4
5
6
7
8const p = new Promise((resovle, reject) => {
reject('error')
})
p.catch(reason => {
console.log(reason)
return reason
})打印结果如下:

catch处理过失败的Promise对象后,返回的是一个成功的Promise对象,该对象的结果值,取决于你在实际代码中返回的值。
Promise.resolve方法
Promise.resolve方法:(value) => {}resolve是在Promise实例对象上的,而不是在Promise的原型对象上resolve参数:value可以是成功状态的结果值或Promise对象如果传入的参数为非
Promise类型的对象(可以是undefined、null等等),则返回的结果为成功的Promise对象1
2const p = Promise.resolve('value')
console.log(p)
如果传入的参数为
Promsie对象,则参数的结果,决定了resolve的结果当参数返回成功时,得到的就是
成功的Promise:1
2
3
4const p = Promise.resolve(new Promise((resolve, reject) => {
resolve(true)
}))
console.log(p)
当参数返回失败时,得到的就是
失败的Promise:1
2
3
4
5
6
7const p = Promise.resolve(new Promise((resolve, reject) => {
reject('Error')
}))
console.log(p)
p.catch(reason =>{
console.log(reason)
})
返回:返回一个
成功/失败的Promise对象作用:为了快速得到一个
Promise对象,并且还能封装一个值,将这个值转化为Promise对象
Promise.reject方法
Promise.reject方法:(reason) => {}reject是在Promise实例对象上的,而不是在Promise的原型对象上reject参数:reason表示失败的原因如果传入的参数为非
Promise类型的对象,返回的是失败的Promise对象1
2
3
4
5
6const p = Promise.reject('value')
console.log(p)
p.catch(reason => {
console.log('reason为:', reason)
})
如果传入的参数为
失败的Promise对象,返回的是失败的Promise对象1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// 定义失败的Promise对象
let p1 = new Promise((resolve, reject) => {
reject('Error')
})
// 处理失败的Promise对象
p1.catch(reason => {
return reason
})
const p2 = Promise.reject(p1) // 将失败的Promise对象传入reject
console.log(p2) // 返回的仍是失败的Promise对象,其结果值是传入的那个Promise对象
p2.catch(reason => {
console.log('reason为:', reason)
})
如果传入的参数为
成功的Promise对象,返回的仍然是失败的Promise对象,只不过reason是你传的那个成功的Promise对象1
2
3
4
5
6
7
8const p = Promise.reject(new Promise((resolve, reject) => {
resolve('OK')
}))
console.log(p)
p.catch(reason => {
console.log('reason为:', reason)
})

返回:始终返回一个
失败的Promise对象作用:为了快速的将一个值,转换成一个失败的
Promise对象。无论这是值是非Promise类型,还是成功/失败的Promise类型。
Promise.all方法
Promise.all方法:(promises) => {}promises参数:包含n个promise的数组返回:返回一个新的
promise,只有所有的promise都成功才成功,只要有一个失败了就直接失败如果数组中的每个
Promise对象状态都是成功,all方法返回的是一个成功的Promise对象,该Promise对象成功的结果值,是每个promise的成功结果值组成的数组1
2
3
4
5
6
7
8let p1 = new Promise((resolve, reject) => {
resolve('OK')
})
let p2 = Promise.resolve('Success')
let p3 = Promise.resolve('Oh Yeah')
const result = Promise.all([p1, p2, p3])
console.log(result)
如果数组中有一个
Promise对象状态是失败,all方法返回的是一个失败的Promise对象,该Promise对象失败的结果值,是那个失败的Promise对象的结果值1
2
3
4
5
6
7
8let p1 = new Promise((resolve, reject) => {
resolve('OK')
})
let p2 = Promise.reject('Error') // 失败的Promise对象
let p3 = Promise.resolve('Oh Yeah')
const result = Promise.all([p1, p2, p3])
console.log(result)返回的是一个失败的
Promise对象
注意:如果我们用链式调用的方式,使用
catch指定了失败Promise对象的回调,则all方法返回的仍是成功的Promise对象1
2
3
4
5
6
7
8
9
10let p1 = new Promise((resolve, reject) => {
resolve('OK')
})
let p2 = Promise.reject('Error').catch(reason => {
return reason
}) // 使用catch处理失败的Promise对象,返回的是一个成功的Promise对象
let p3 = Promise.resolve('Oh Yeah')
const result = Promise.all([p1, p2, p3])
console.log(result)
因为失败的
Promise一旦用catch指定失败的回调,其返回值是一个成功的Promise对象所以在处理失败的
Promise对象时,不能用链式调用的写法,要分开来写才行,这样p2就是一个失败的Promise对象了1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17let p1 = new Promise((resolve, reject) => {
resolve('OK')
})
let p2 = Promise.reject('Error')
p2.catch(reason => {
return reason
}) // 分开来写
let p3 = Promise.resolve('Oh Yeah')
const result = Promise.all([p1, p2, p3])
console.log(result)
result.catch(reason => {
return reason
})
备注:如果有两个失败的
Promise对象,则返回的结果值是优先失败的那个Promise对象的结果值,这里返回的是p3的结果值1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20let p1 = new Promise((resolve, reject) => {
resolve('OK')
})
let p2 = Promise.reject('Error') // 失败的Promise
p2.catch(reason => {
return reason
})
let p3 = Promise.reject('Oh Yeah') // 失败的Promise
p3.catch(reason => {
return reason
})
const result = Promise.all([p1, p3, p2]) // 将p3放在前面
console.log(result)
result.catch(reason => {
return reason
})
Promise.race方法
Promise.race方法:(promises) => {}promises参数:包含n个promise的数组返回:返回一个新的
Promise,第一个完成的Promise的结果状态,就是最终的结果状态1
2
3
4
5
6
7
8
9
10let p1 = new Promise((resolve, reject) => {
resolve('OK')
})
let p2 = Promise.resolve('Success')
let p3 = Promise.resolve('Oh Yeah')
const result = Promise.race([p1, p2, p3])
console.log(result)
p1先完成,race返回的就是p1的状态结果我们给
p1添加一个定时器1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK')
}, 2000)
})
let p2 = Promise.reject('Error')
p2.catch(reason => {
return reason
})
let p3 = Promise.resolve('Oh Yeah')
const result = Promise.race([p1, p2, p3])
console.log(result)
result.catch(reason => {
return reason
})
p2先完成,race返回的就是p2的结果
2.3.2Promise的几个关键问题
如何改变Promise的状态
resolve(value):如果当前是pending,就会变成resolved1
2
3
4let p = new Promise((resolve, reject) => {
// 1.resolve函数
resolve('OK') // pending ==> fulfilled
})reject(reason):如果当前是pending,就会变成rejected1
2
3
4let p = new Promise((resolve, reject) => {
// 2.reject函数
resolve('Error') // pending ==> rejected
})通过
throw抛出了异常:如果当前是pending,就会变成rejected1
2
3
4let p = new Promise((resolve, reject) => {
// 3.throw抛出异常
throw 'Error' // pending ==> rejected
})
能否执行多个回调
如果使用
then方法,为一个Promise对象指定多个成功/失败回调函数,都会调用吗?当
Promise改变为对应状态时都会调用1
2
3
4
5
6
7
8
9
10
11
12let p = new Promise((resolve, reject) => {
resolve('OK') // pending => fulfilled
})
// 指定回调1
p.then(value => {
console.log(value + '1')
})
// 指定回调2
p.then(value => {
console.log(value + '2')
})结果如下:

如果不调用
resolve,则状态一直是pending,则回调函数也不会被调用
改变Promise状态和指定回调函数谁先谁后
1 | let p = new Promise((resolve, reject) => { |
问:resolve('OK')改变状态,和value => {}指定回调,谁先执行?
都有可能,正常情况下是先指定回调再改变状态,但也可以先改变状态再指定回调
如何改变
Promise状态:resolve()reject()throw
如何指定回调:
then()catch()
什么时候先改变状态,再指定回调?
在执行器函数中,直接调用
resolve()/reject()当执行器函数里面的任务,是同步任务的时候
1
2
3
4
5
6
7
8
9
10let p = new Promise((resolve, reject) => {
resolve('OK') // 先改变状态
})
p.then(value => {
// 后指定回调
console.log(value)
}, reason => {
})延迟更长时间才调用
then(),这个例子中,我们可以延迟2秒再执行then()方法
什么时候先指定回调,后改变状态?
当执行器函数里面的任务,是异步任务,需要进入队列的时候
1
2
3
4
5
6
7
8
9
10
11
12
13
14let p = new Promise((resolve, reject) => {
// 后改变状态
setTimeout(() => {
resolve('OK')
}, 2000)
})
p.then(value => {
// 先指定回调
// 等1秒,输出value
console.log(value)
}, reason => {
})进行文件操作、数据库操作、
AJAX操作时,执行器函数里面的,都是异步任务
什么时候才能得到数据?(回调函数什么时候执行)
- 执行器函数中,可以是同步任务,也可以是异步任务
- 如果先指定的回调,那当状态发生改变时,回调函数就会调用,得到数据(异步任务)
- 如果先改变状态,那当指定回调时,回调函数就会调用,得到数据(同步任务)
Promise.then()返回的新的Promise的结果状态,由什么决定?
简单表达:由
then()指定的回调函数执行的结果决定详细表达:
then方法的返回值:为一个新的Promise对象如果
then方法的回调函数中,抛出异常,新Promise状态变为rejected,结果值reason为抛出的异常1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16let p = new Promise((resolve, reject) => {
resolve('ok')
})
// 执行then方法
let result = p.then(value => {
throw 'Error'
}, reason => {
console.log(reason)
})
console.log(result)
result.catch(reason => {
return reason
})
如果
then方法的回调函数中,返回的是非Promise的任意值,新Promise状态变为resolved/fulfilled,结果值value为返回的值1
2
3
4
5
6
7
8
9
10
11
12
13let p = new Promise((resolve, reject) => {
resolve('ok')
})
// 执行then方法
let result = p.then(value => {
return 521
}, reason => {
console.log('reason为:' + reason)
})
console.log(result)
如果
then方法的回调函数中,返回的是另一个新的Promise,此Promise的结果就会成为新的Promise的结果1
2
3
4
5
6
7
8
9
10
11
12
13
14
15let p = new Promise((resolve, reject) => {
resolve('ok')
})
// 执行then方法
let result = p.then(value => {
return new Promise((resolve, reject) => {
resolve('success')
})
}, reason => {
console.log('reason为:' + reason)
})
console.log(result)
如果
then方法的回调函数中,即没有抛出异常,也没有return,则新的Promise对象的结果值为undefined
Promise如何串联多个操作任务
Promise的then()返回一个新的Promise,可以写成then()的链式调用1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK')
}, 1000)
})
p.then(value => {
console.log(value) // 延迟一秒,OK
return new Promise((resolve, reject) => {
resolve('success')
})
}).then(value => {
console.log(value) // success
}).then(value => {
console.log(value) // undefined
})
通过
then()的链式调用串联多个同步/异步任务
Promise异常穿透
当使用
Promise的then链式调用时,可以在最后指定失败的回调1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18let p = new Promise((resolve, reject) => {
setTimeout(() => {
reject('Error')
}, 1000)
})
p.then(value => {
console.log(value)
return new Promise((resolve, reject) => {
resolve('success')
})
}).then(value => {
console.log(value)
}).then(value => {
console.log(value)
}).catch(reason => {
console.log(reason) // 1秒后 Error
})
前面任何操作出现了异常,都会传到最后失败的回调中处理
- 如果出现了多个异常,只捕获第一个异常
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18let p = new Promise((resolve, reject) => {
setTimeout(() => {
reject('Error')
}, 1000)
})
p.then(value => {
console.log(value)
return new Promise((resolve, reject) => {
reject('Error02')
})
}).then(value => {
console.log(value)
}).then(value => {
console.log(value)
}).catch(reason => {
console.log(reason) // 1秒后 Error,不会输入Error02
})
如何中断Promise链
当使用
Promise的链式调用时,在中间中断,不再调用后面的回调函数唯一办法:
在回调函数中返回一个
pendding状态的Promise对象pendding状态,意味着状态没有发生改变,也就不会执行回调函数了代码:
return new Promise((resolve, reject) => {})1
2
3
4
5
6
7
8
9
10
11
12
13
14let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK')
}, 1000)
})
p.then(value => {
console.log(111) // 只会输出111
return new Promise((resolve, reject) => {})
}).then(value => {
console.log(222) // 不会被调用
}).then(value => {
console.log(333) // 不会被调用
})
3.手写Promise
3.1.定义整体结构
1 | |----index.html |
index.html
1 |
|
promise.js
1 | function Promise() { |
报错信息如下,因为我们没有在构造函数Promise的显示原型属性上定义then方法,所以实例对象p的隐式原型属性上自然就没有该then方法

构造函数Promise的显示原型属性上,添加then方法,该then方法有两个形参:onResolved、onRejected
同时也给Promise构造函数,指定一个形参:executor
promise.js
1 | function Promise(executor) { |
至此,基本结构搭建完毕
3.2.resolve和reject结构搭建
promise.js
1 | function Promise(executor) { |
注意:在当前作用域当中,并没有resolve和reject变量
所以需要在函数内部声明,这两个变量都是函数类型的变量
promise.js
1 | function Promise(executor) { |
3.3.resolve和reject代码实现
resolve函数执行完之后,有什么效果?Promise的状态会发生改变,有pending变为fulfilled- 可以设置
Promise对象的结果值
- 我们需要添加
promiseState和promiseResult这两个属性
promise.js
1 | function Promise(executor) { |
在index.html中测试看下效果
1 |
|
结果如下:

按道理来说,PromiseResult应该是OK,PromiseState应该是fulfilled
为什么没有变化呢?
是因为resolve函数中,this指向window
1 | function Promise(executor) { |
结果:

此时,调用resolve,Promise对象的状态和结果都发生了改变
完善一下reject
1 | function Promise(executor) { |
3.4.throw抛出异常改变状态
如果直接抛出异常,则会报错
1 | <script> |

正常来说,应该打印一个失败的Promise对象
我们使用try catch来处理throw,加在执行器函数执行的地方
1 | function Promise(executor) { |
打印结果:

3.5.Promise状态只能修改一次
我们现在封装的代码,如果先调用了resolve,再调用reject,Promise对象的状态会改变两次
1 | <script> |
最终的结果(这是错误的):

那么应该怎么做,可以确保状态值更改一次呢?
应该在改变状态之前,加一层判断,判断是否已经更改过了
1 | function Promise(executor) { |
结果如下(这个是对的):

3.6.then方法执行回调
此时我们封装的then方法,是不能执行回调的
index.html
1 |
|
结果是没有打印输出的
为啥呢?因为我们还没写调用的代码!
应该在then方法中调用回调函数
1 | function Promise(executor) { |
打印结果:

3.7.异步任务回调的执行
我们在执行器函数中定义异步任务
1 |
|
此时并没有打印输出结果
代码从上往下走,resovle函数并没有立即执行
再往下走,p的状态并没有发生变化,依旧是pending状态,但我们并没有对pending状态做任何处理
所以,在then方法中我们需要对pending状态进行判断
1 | // 判断pending状态 |
那么,我们需要做什么操作呢?
对于异步任务来说,什么时候调用回调函数呢?应该是在状态发生改变的时候,那么什么时候状态发生改变呢?
很明显,是在调用resovle/reject函数的时候,
所以我们应该在resolve/reject函数内,调用回调函数
1 | function resolve(data) { |
那么,怎么样才可以在resovle内,调用到then方法的回调函数呢?这两个函数是在不同的作用域内的
所以,我们在判断pending状态后,应该将回调函数保存下来
那么,应该保存在哪儿呢?在then方法外部定义一个变量吗?不!我们直接给Promise构造函数声明一个属性,实例化时保存在实例对象上
1 | function Promise(executor) { |
运行完then方法后,打印输出p:
1 |
|
打印结果:

对象p已经有了回调函数
之后,我们就可以在resolve/reject函数中,执行对应的回调函数了
1 | function Promise(executor) { |
此时再看下打印效果,一秒后,输出结果:

3.8.指定多个回调的实现
index.html
1 |
|
目前我们的代码是不能够执行多个回调的,最后的then方法中的回调,会将之前的覆盖掉,只会执行最后一次的回调
所以我们之前那种保存回调函数的方式,需要修改一下:
将callback定义成数组,每次保存回调时push进去一个对象即可
1 | this.callbacks = [] // 回调函数保存在实例对象的callback属性上 |
在then方法后面,打印一下对象p

此时两个回调函数都被保存到了实例对象上
在resovle/reject函数中,重新调用下相应的回调函数
1 | function Promise(executor) { |
打印结果如下:

两次回调函数都被执行了
3.9.同步任务下的then方法返回结果实现
index.html
1 |
|
目前res是undefined,因为我们在then方法中,并没有返回任何的值

所以我们在then方法中,返回一个Promise对象
1 |
|
此时res的结果如下:

有个问题,就是then方法返回的Promise对象的状态,一直是pending
我们需要根据then方法回调函数的执行结果,来改变then方法返回的Promise对象的状态
所以要定义一个变量result保存回调函数执行的结果
1 | let result = onResolved(this.PromiseResult) |
对结果进行类型判断,并对状态进行设置
1 | function Promise(executor) { |
打印结果如下:

如果then方法的回调函数,返回的是Promise类型的对象呢?
1 | function Promise(executor) { |
index.html
1 |
|
打印结果如下:

如果then方法的回调函数,抛出了异常呢?
使用try catch进行包裹处理
1 | function Promise(executor) { |
index.html
1 |
|
打印结果如下:

3.10.异步任务下的then方法返回结果实现
index.html
1 |
|
打印结果是pending状态的Promise:

由于是异步任务,对象p在调用then方法时,是一个pending状态,目前我们自定义的代码,对于pending状态 操作,仅做了一个保存回调函数的操作
1 | if (this.PromiseState === 'pending') { |
我们需要修改一下pending状态下回调的代码
1 | if (this.PromiseState === 'pending') { |
验证下onResoved函数在回调执行的时候,的确是被调用了:

我们真正要做的,是要执行成功的回调函数(注意this的指向问题)
1 | Promise.prototype.then = function (onResolved, onRejected) { |
但此时返回的Promise对象的状态仍然是pending

和上一小节类似,要根据函数的执行结果来决定状态
1 | function Promise(executor) { |
我们在then方法成功的回调函数中,返回一个值
1 |
|
打印结果如下:

完善一下onRejected,同时做一下异常处理
1 | function Promise(executor) { |
3.11.then方法的完善与优化
对
rejected状态情况进行完善1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121function Promise(executor) {
this.PromiseState = 'pending'
this.PromiseResult = null
this.callbacks = []
const self = this
function resolve(data) {
if (self.PromiseState !== 'pending') return
self.PromiseState = 'fulfilled'
self.PromiseResult = data
self.callbacks.forEach(item => {
item.onResolved(data)
})
}
function reject(data) {
if (self.PromiseState !== 'pending') return
self.PromiseState = 'rejected'
self.PromiseResult = data
self.callbacks.forEach(item => {
item.onRejected(data)
})
}
try {
executor(resolve, reject)
} catch (e) {
reject(e)
}
}
Promise.prototype.then = function (onResolved, onRejected) {
const self = this
return new Promise((resolve, reject) => {
if (this.PromiseState === 'fulfilled') {
let result = onResolved(this.PromiseResult)
// 使用try catch进行包裹处理
try {
if (result instanceof Promise) {
result.then(v => {
resolve(v)
}, r => {
reject(r)
})
} else {
resolve(result)
}
} catch (e) {
reject(e)
}
}
if (this.PromiseState === 'rejected') {
try {
// 对rejected的情况进行完善
let result = onRejected(this.PromiseResult)
if (result instanceof Promise) {
result.then(v => {
resolve(v)
}, r=> {
reject(r)
})
} else {
resolve(result)
}
} catch (e) {
reject(e)
}
}
if (this.PromiseState === 'pending') {
this.callbacks.push({
onResolved: function () {
// 处理抛出的异常
try {
// 异步任务的pendding状态下,先调用成功的回调函数
let result = onResolved(self.PromiseResult)
// 根据回调函数的执行结果来决定状态
if (result instanceof Promise) {
result.then(v => {
resolve(v)
}, r => {
reject(r)
})
} else {
resolve(result)
}
} catch (e) {
reject(e)
}
},
onRejected: function () {
try {
let result = onRejected(self.PromiseResult)
// 根据回调函数的执行结果来决定状态
if (result instanceof Promise) {
result.then(v => {
resolve(v)
}, r => {
reject(r)
})
} else {
resolve(result) // 这里不需要改成reject(result)
}
} catch (e) {
reject(e)
}
}
})
}
})
}重复代码的封装
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79function Promise(executor) {
this.PromiseState = 'pending'
this.PromiseResult = null
this.callbacks = []
const self = this
function resolve(data) {
if (self.PromiseState !== 'pending') return
self.PromiseState = 'fulfilled'
self.PromiseResult = data
self.callbacks.forEach(item => {
item.onResolved(data)
})
}
function reject(data) {
if (self.PromiseState !== 'pending') return
self.PromiseState = 'rejected'
self.PromiseResult = data
self.callbacks.forEach(item => {
item.onRejected(data)
})
}
try {
executor(resolve, reject)
} catch (e) {
reject(e)
}
}
Promise.prototype.then = function (onResolved, onRejected) {
const self = this
return new Promise((resolve, reject) => {
// 封装函数,注意内部this的指向问题
function callback(type) {
// 使用try catch进行包裹处理
try {
let result = type(self.PromiseResult) // 封装复制的时候,result的定义要放在try的后面
if (result instanceof Promise) {
result.then(v => {
resolve(v)
}, r => {
reject(r)
})
} else {
resolve(result)
}
} catch (e) {
reject(e)
}
}
if (this.PromiseState === 'fulfilled') {
callback(onResolved)
}
if (this.PromiseState === 'rejected') {
callback(onRejected)
}
if (this.PromiseState === 'pending') {
this.callbacks.push({
onResolved: function () {
callback(onResolved)
},
onRejected: function () {
callback(onRejected)
}
})
}
})
}
3.12.catch方法异常穿透与传值
index.html
1 |
|
此时会报错:

因为我们自定义的代码中,还没有添加catch方法
添加catch:我们在内部调用then方法即可
1 | function Promise(executor) { |
catch方法返回的也是Promise对象
index.html
1 |
|
打印结果如下:

我们还需要实现异常穿透的功能
index.html
1 |
|
打印结果如下:

原因:
代码执行到then之前的时候,对象p是一个pending的状态,代码中我们把两个回调存起来了:
1 | if (this.PromiseState === 'pending') { |
但此时,成功的回调是有的,失败的回调我们没写,所以是一个undefined,当状态改变完之后,一定会调用回调:
1 | self.callbacks.forEach(item => { |
而这个item里面,只有成功的回调,而没有失败的回调。实际调用的是就是undefined,就有问题了
所以我们在封装时,要允许then方法不传第二个回调函数,then方法在return之前,需要对onRejected的回调做一下判断
1 | Promise.prototype.then = function (onResolved, onRejected) { |
当没有失败的回调时,我们自定义一个默认的回调,这个默认的回调中,抛出了异常的结果值。
这样当链式调用then方法,就相当于如下情况
1 | p.then(value => { |
然后在then方法中一直抛,直到遇到catch方法,可以将异常处理掉
1 | Promise.prototype.catch = function(onRejected) { |
打印结果如下:

如果异常是发生在链式调用的中间,也是可以实现异常穿透的
index.html
1 |
|
打印效果如下:

除了可以进行异常穿透外,还有一个特性就是值传递
即如下代码,也是可以可以正常的执行的
index.html
1 |
|
但我们自定义的是不行的,打印结果如下:

原因和上面一样,所以我们也要指定默认的成功回调函数
1 | function Promise(executor) { |
打印结果正常:

3.13.封装resolve方法
index.html
1 |
|
报错:

因为我们自定义的代码还没有添加呢
1 | function Promise(executor) { |
测试一下结果,返回的结果的确是一个Promise对象

接下来我们根据传入的value来更改状态
1 | function Promise(executor) { |
此时打印结果的状态,不再是pending了,并且当传入的value是一个Promise对象时,也是正常的
1 |
|
打印结果如下:

resolve的嵌套调用也是支持的
1 |
|

3.14.封装reject方法
类似resovle方法的封装
1 | // ... |
测试效果:
1 |
|

3.15.封装all方法
1 |
|
此时我们的all方法还没有定义,定义all方法:
1 | // ... |
此时看下打印结果:

3.16.封装race方法
新增race方法
1 |
|
测试:
1 |
|
结果:

对象p2先执行完,就先返回p2
3.17.then方法回调函数异步执行
原生实现:
1 |
|
打印结果:

自定义实现打印结果:

then方法的回调应该是异步执行的,我们给对应执行的代码添加定时器:
1 | function Promise(executor) { |
此时的结果在表现上,就和原生的一样了:

3.18.Promise完整实现
1 | function Promise(executor) { |
3.19.class版本的实现
promise_class.js
1 | class Promise { |
调用测试一下:
1 |
|
结果正常

4.async与await
4.1.mdn文档
async function - JavaScript | MDN (mozilla.org)
await - JavaScript | MDN (mozilla.org)
4.2.async函数
函数的返回值为一个
Promise对象1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<script>
async function main() {
}
let result = main()
console.log(result)
</script>
</body>
</html>
Promise对象的结果由async函数执行的返回值决定如果返回值是一个非
Promise类型的数据:- 最终的返回结果就是你返回的那个值,
- 最终返回的状态时为成功
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<script>
async function main() {
return 'OK'
}
let result = main()
console.log(result)
</script>
</body>
</html>
如果返回值是一个
Promise类型的对象,最终async函数的返回结果和该Promise对象返回的结果一致1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<script>
async function main() {
return new Promise((resolve, reject) => {
resolve('Success')
})
}
let result = main()
console.log(result)
</script>
</body>
</html>
如果抛出了异常,最终返回的结果就是一个失败的
Promise对象,结果值就是抛出的结果值1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<script>
async function main() {
throw 'Oh NO'
}
let result = main()
console.log(result)
</script>
</body>
</html>
报错是因为我们没有
catch处理异常
这个规则,和
then方法中成功状态回调函数的返回规则是一样的
4.3.await表达式
await右侧的表达式一般为Promise对象,但也可以是其它的值如果表达式是
Promise对象,await返回的是Promsie成功的值1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<script>
async function main() {
let p = new Promise((resolve, reject) => {
resolve('OK')
})
// 右侧为promise的情况
let res = await p
console.log(res)
}
main()
</script>
</body>
</html>
如果表达式是其它的值,直接将此值作为
await的返回值1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<script>
async function main() {
let p = new Promise((resolve, reject) => {
resolve('OK')
})
// 右侧为其他类型的数据
let res = await 100
console.log(res)
}
main()
</script>
</body>
</html>
4.4.注意
await必须写在async函数中,但async函数中可以没有await1
2
3
4
5
6
7
8
9
10
11
12
13
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<script>
await 100
</script>
</body>
</html>
如果
await的Promise对象失败了,就会抛出异常,需要使用try catch捕获处理1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<script>
async function main() {
let p = new Promise((resolve, reject) => {
reject('Error')
})
// 如果Promise状态为失败,需要使用try catch来捕获异常
try {
let res = await p
} catch(e) {
console.log(e)
}
}
main()
</script>
</body>
</html>
4.5.async和await结合实践
需求:读取三个文件的内容,拼接后输出
1.html
1 | 行宫 |
2.html
1 | 寥落古行宫,宫花寂寞红。 |
3.html
1 | 白头宫女在,闲坐说玄宗。 |
纯回调函数实现
1
2
3
4
5
6
7
8
9
10
11const fs = require('fs')
fs.readFile('./resource/1.html', (err, data1) => {
if(err) throw err
fs.readFile('./resource/2.html', (err, data2) => {
if(err) throw err
fs.readFile('./resource/3.html', (err, data3) => {
console.log(data1 + data2 + data3)
})
})
})async + await实现1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16const fs = require('fs')
const util = require('util')
const myReadFile = util.promisify(fs.readFile)
async function main() {
try {
let data1 = await myReadFile('./resource/1.html')
let data2 = await myReadFile('./resource/2.html')
let data3 = await myReadFile('./resource/3.html')
console.log(data1 + data2 + data3)
} catch(e) {
console.log(e)
}
}
main()首先不需要写那么的回调函数,并且也不需要每一层都对异常进行处理
我们故意写错第一个路径,直接用
try catch处理异常即可,处理异常的方式变得异常灵活
我们在
async和await的使用中是看不到回调函数的(在Promise里面是有的),写起来非常的简洁,就像写同步代码一样(内部的执行是异步的)
4.6.async和await结合发送ajax请求
需求:刷新下页面,就发送一次请求
拿到我们之前封装成Promise的发送ajax请求的方法,并使用await在async函数中发送请求
1 |
|
结果:

封装成Promise对象后,代码的书写非常的简洁
并且,将网络请求封装成Promise对象,已经有现成的工具库了Axios,这是一个基于Promise的Ajax封装的包
使用axios发送请求时
1 | async categoryList() { |
5.JS异步之宏队列与微队列
5.1.原理图

5.2.队列分类
JS中用来存储待执行回调函数的队列包含2个不同特定的队列
异步任务会放入队列中执行
- 宏队列
- 用来保存待执行的宏任务(回调)
- 回调函数的种类:
dom事件回调ajax回调- 定时器回调
- 放入宏队列中的回调函数称为宏任务
- 微队列
- 用来保存待执行的微任务(回调)
- 回调函数的种类:
Promise回调mutation回调- 监视的是标签属性的改变
- 放入微队列中的回调函数称为微任务
5.3.执行顺序
JS执行时会区别这2个队列JS引擎必须先执行所有的初始化同步任务代码- 同步代码还没执行完时,队列里是可以先有任务的,只不过不会执行
- 只有将同步代码全部执行完之后,才会执行队列里面的回调函数
再执行队列里的代码
- 每次准备取出第一个宏任务执行前,都要将所有的微任务一个一个取出来执行
微任务相比较于宏任务,优先执行
1 | setTimeout(() => { // 会立即放入宏队列 |

宏任务与微任务同级之间,是按次序执行的
1 | setTimeout(() => { // 会立即放入宏队列 |

前置宏任务内部如果有微任务,后置宏任务会后执行
1 | setTimeout(() => { // 会立即放入宏队列 |

5.4.Promise面试题
面试题一
1 | setTimeout(() => { |

面试题二
Promise的构造器函数的执行时同步的,所以先输入2
1 | setTimeout(() => { |

面试题三
Promise对象状态改变过一次后,后面就不会再改变了,回调函数也不再被调用,所以最后的resovle(6)不会有任何效果
1 | const first = () => (new Promise((resolve, reject) => { |

面试题四
当Promise对象的pending状态发生改变时,其第一个then方法的回调函数会进入微队列最后,第二个及后面的then方法依次执行时,其各自的回调函数会被缓存。
被缓存的回调函数,在第一个then方法的回调对应的微任务被执行完,该then方法返回的Promise对象状态发生了改变时,被缓存的第二个then方法的回调,进入微队列的末端,等待之前的微任务执行完毕后开始执行。
1 | // 1 7 2 3 8 4 6 5 0 |




