来聊聊promise

什么是promise

promise是es6的一个重要特性,用于实现异步。promise对象拥有一个叫做状态的属性,该属性不受外界影响,修改后不能再次变化。而Promise是一个构造函数,可以生成promise对象。
常见用法是

1
2
3
4
5
6
7
8
9
10
11
12
var a = new Promise(function (res, rej) {
console.log('1')
if (1) {
res(3)
} else {
rej()
}
})
a.then(function (val) {
console.log(val)
})
console.log(2)

输出顺序是1,2,3,为什么呢?
new Promise接受的参数函数是同步执行的,因此会马上输出1,而then中的方法是被放到mircotask队列中,在当前的代码执行完后在执行。因此先输出2,再输出3

现在让我们来打印一下Promise,看看他还有什么别的功能

img

构造函数的四个方法

从图片中我们可以发现Promise构造函数上带有resolve,race,all,reject四个方法,他们的共同点是都会返回一个promise。让我们来分别了解下。

resolve

先来了解下resolve

resolve接受一个值或是promise对象,如果接受的是promise对象,会直接返回该promise对象,否则返回完成状态的promise对象。

1
2
3
Promise.resolve(3).then(res => console.log(res))// 3
var a = Promise.resolve(3)
console.log(Promise.resolve(a) === a) // true
reject

类似resolve,返回的是拒绝状态的promise

all

Promise.all接受一个数组,分为三种情况

  • 传入空数组,返回完成状态的promise
  • 传入的数组中没有promise,返回异步完成的prmise
  • 传入的数组中有promise,返回处于pending状态的promise对象,在数组中的promise都成功或者有一个失败时,变成完成状态或拒绝状态

试验一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 情况1
var a = []
var b = (Promise.all(a)) // promise{ [] }
console.log(b)
setTimeout(() => console.log(b), 0) // promise{ []}
// 情况2
var a = [3, 4]
var b = (Promise.all(a)) // promise{ <pending> }
console.log(b)
setTimeout(() => console.log(b), 0) // promise{ []}
// 情况3
var a = [
Promise.resolve(3),
Promise.resolve(4)
]
var b = (Promise.all(a)) // promise{ <pending> }
console.log(b)
setTimeout(() => console.log(b), 0) // promise{ [3,4]}
race

同all有点接近,在某个promise完成后,返回该值,根据状态来决定返回的是完成状态还是拒绝状态的promise

如果传入的是空数组,返回的promise永远是等待

1
2
3
4
var a = []
var b = (Promise.race(a)) // promise{ <pending> }
console.log(b)
setTimeout(() => console.log(b), 0) // promise{ <pending> }

promise对象的方法

promise带有then和catch两个方法

then

then接受两个参数,返回一个promise。
第一个参数是promise在成功的情况下的回调函数,第二个参数是失败情况下的(可选)

  • 如果then中的回调函数返回一个值,那么then返回的Promise将会成为接受状态,并且将返回的值作为接受状态的回调函数的参数值。
  • 如果then中的回调函数抛出一个错误,那么then返回的Promise将会成为拒绝状态,并且将抛出的错误作为拒绝状态的回调函数的参数值。
  • 如果then中的回调函数返回一个已经是接受状态的Promise,那么then返回的Promise也会成为接受状态,并且将那个Promise的接受状态的回调函数的参数值作为该被返回的Promise的接受状态回调函数的参数值。
  • 如果then中的回调函数返回一个已经是拒绝状态的Promise,那么then返回的Promise也会成为拒绝状态,并且将那个Promise的拒绝状态的回调函数的参数值作为该被返回的Promise的拒绝状态回调函数的参数值。
  • 如果then中的回调函数返回一个未定状态(pending)的Promise,那么then返回Promise的状态也是未定的,并且它的终态与那个Promise的终态相同;同时,它变为终态时调用的回调函数参数与那个Promise变为终态时的回调函数的参数是相同的。
catch

Promise.prototype.then(undefined, onRejected)一致

promise对象的属性

打印一下a,可以看到,一个代表当前状态,一个代表值,都是不能修改的。

1
2
[[PromiseStatus]]:"resolved"
[[PromiseValue]]:3

而将res(3)这行代码注释掉的话,a变成

1
2
[[PromiseStatus]]:"pending"
[[PromiseValue]]:undeined

而在下面的代码中,

1
2
3
var a = new Promise(function (res, rej) {
rej(1)
})

得到的a

1
2
[[PromiseStatus]]:"rejected"
[[PromiseValue]]:4

promise与任务队列

先看一个案例

1
2
3
4
5
6
7
8
9
10
11
console.log(0)
var a = new Promise(function (res, rej) {
console.log('1')
setTimeout(() => console.log('4'))
res(3)
})
a.then(function (val) {
console.log(val)
})
console.log(2)

输出结果是0,1,2,3,4,解释一下过程。
JS执行分为mircotask和marcotask两个任务队列,其中promise属于mircotask,而js主线程代码和setTimeout属于marcotask。

这段代码的运行流程是这样的:

  • 运行代码(marcotask)
  • 碰到第一个console,运行,输出0
  • 碰到promise构造函数,运行构造函数的参数中的函数
    • 先是输出1
    • 接着是碰到setTimeout,将其存放在marcotask队列
    • 接着是调用res函数,将promise的状态设置为fulfilled,返回promise对象给a
  • 调用a的then方法,then中的参数的函数被放在了mircotask中
  • 输出最后的2,至此,当前的marcotask执行完成。
  • 执行mircotask中的函数,输出3
  • 执行marcotask中的函数,输出4

我们可以修改一下函数来加深下印象

1
2
3
4
5
6
7
8
9
10
11
12
13
console.log(0)
var a = new Promise(function (res, rej) {
console.log('1')
setTimeout(() => console.log('4'))
res(3)
})
a.then(v => loop(a))
function loop (p) {
var i = 0
while (i++ < 5)
p.then(v => console.log(v))
}
console.log(2)

可以发现,最后是输出了5个3,他的执行过程跟刚刚的很接近,区别在于倒数第二步,在调用mircotask队列中的loop函数时,又加入新的mircotask,而只有mircotask中的任务都完成后,才能执行marcotask中的函数。

promise的优缺点

优点
  1. 跟callback相比,避免的回调地域无限嵌套,可以使用链式写法
  2. 约束了异步处理的写法
  3. 便于捕捉错误
缺点
  1. 无法取消promise
  2. 无法处理多次触发的事件
  3. 无法获取当前执行的进度信息

注意,一些情况下可以使用Promise.race来取消promise。比如设置异步请求在三秒不成功的话取消,可以在Promise.race的第二个参数加一个三秒的定时器。

hpoenixf wechat
扫码获取最新博客推送
支持作者