LOADING...

DIARY LOG 2018.09.27

Promise的相关小结

前言

网上关于Promise的文章确实是非常多了,但是自己实践的并不多,这里是针对自己的一个知识点小结和梳理,而且由Promise延伸出的关于事件循环的机制也是可以挖出很多东西的,不过这个总结可能就是涉及到一些皮毛,当然啦如果有错误欢迎提出^_^。

初定义

定义:Promise对象用于一个异步操作的最终完成/失败及其结果值的表示。
**使用原因:**避免回调嵌套层次过多。
拥有状态:

  • pending:初始/未定状态,初始化Promise时,调用executor函数后的状态。
  • fulfilled:成功状态。
  • rejected:失败状态。

状态转化:

  • pending -> fulfilled:操作成功
  • pending -> rejected:操作失败
    状态转化是单向的,不可逆转。

最基本用法:
可以看到创建一个Promise实例,传入的参数是一个函数,这个函数称为executor/执行器。

1
2
3
4
5
6
7
new Promise((resolve, reject) => {
if (success) {
resolve(a) // pending to resolved
} else {
reject(err) // pending to rejectd
}
})

方法

Promise对象本身,有一些方法:

  • race()
  • reject()
  • resolve()
  • all()

查看Promise的原型,发现它内置有几个方法:

  • catch()
  • finally()
  • then()

Promise.prototype.then()

  • 参数:处理成功的函数,处理错误的函数
  • 返回值:返回一个Promise对象,所以可以链式调用。
1
2
3
promise.then(
() => { console.log('我是成功后被执行的') },
() => { console.log('我是失败后被执行的') })

Promise.prototype.catch()

  • 参数:捕捉的错误/reject()传来的参数
  • 返回值:返回一个Promise对象,所以可以链式调用。
  • Promise和then()中抛出错误能够不断传递,就能够在下一个catch()中统一处理,所以一般省略then中的第二个失败执行的函数。
1
2
3
4
promise.then(
() => { console.log('我是成功后被执行的') }
).catch(
(err) => { console.log(err) })

使用rejects()方法改变状态和抛出错误 throw new Error() 的作用是相同的

Promise.all()

  • 参数:可迭代参数,如:数组。
  • 用途:处理一些并发的异步操作,需要保证每个都执行完毕。
  • 结果:状态全为fulfilled->fulfilled,否则->rejected。

Promise.race()

  • 参数:可迭代参数,如:数组。
  • 用途:处理一些并发的异步操作,只需要其中一个执行完毕。
  • 结果:所有异步操作中有一个状态先改变,就采纳那个最先改变的状态为结果。

Promise.resolve()

  • 参数:普通值、Promise对象、带有then的对象。
  • 结果:一般情况返回一个状态为fulfilled的Promise对象。解析发生错误则返回rejected的Promise对象。
1
2
3
4
5
6
7
8
Promise.resolve('success')
// 其中[[PromiseStatus]]:"resolved"

Promise.reject('fail')
// 其中[[PromiseStatus]]:"rejected"

Promise.resolve(Promise.reject('fail'))
// 其中[[PromiseStatus]]:"rejected"

由这个例子可以看出浏览器认为resolvedfulfilled是等价的,但Promise.resolve() 不一定让promise最终是fulfilled。所以对于resolved本身和fulfilled的区别,可以理解为resolved等价于compiled,即可能是成功也可能是失败。

Promise.reject()

  • 参数: 发生异常的原因。
  • 结果:返回一个rejected状态的Promise对象。

注意点

状态变化

  • Promise状态只会改变一次。
  • 构造函数中的resolve()/reject()只有第一次执行有效,多次调用没有作用。
  • Promise状态改变,并且传递了一个值,后续调用.then()/.catch()都可直接拿到该值。

参数/返回值

  • .then()/.catch()的参数应该是函数,传入非函数则会发生值穿透。
1
2
3
4
Promise.resolve(1)
.then(2)
.then(Promise.resolve(3))
.then(console.log) //1
  • .then()/.catch()不能返回Promise本身,会造成死循环。
  • .then()/.catch()中return一个error对象并不会抛出错误,所以无法捕捉。

因为返回任意一个非Promise 的值都会被包裹成Promise对象,即 return new Error(‘error!!!’)等价于return Promise.resolve(new Error(‘error!!!’))

执行顺序

  • Promise构造函数是同步执行的,resolve()/reject()后的代码也会执行。Promise.then()中的函数是异步执行的。**
    以下输出:1243
1
2
3
4
5
6
7
8
9
const promise = new Promise((resolve, reject) => {
console.log(1)
resolve()
console.log(2)
})
promise.then(() => {
console.log(3)
})
console.log(4)
  • process.nextTickpromise.then属于microtasksetImmediate属于 macrotask。在每一次事件循环中,macrotask只会提取一个执行,而microtask会一直提取,直到microsoft队列为空为止。

以下输出:end nextTick then setTimeout1 setTimeout2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
process.nextTick(() => {
console.log('nextTick')
})

setTimeout(() => {
console.log('setTimeout1')
})

Promise.resolve()
.then(() => {
console.log('then')
})

setTimeout(() => {
console.log('setTimeout2')
})

console.log('end')

补充:
macrotasks:

  • setTimeout
  • setInterval
  • setImmediate
  • requestAnimationFrame
  • I/O
  • UI rendering

microtasks:

  • process.nextTick
  • Promises
  • Object.observe
  • MutationObserver

手写Promise

构造函数
首先我们要知道Promise本身拥有的属性:
1.状态(Pending、Fullfied、Rejected),在状态发生改变后,就不会再进行变化了。
2.本次Promise的执行结果,value存储
3.我们最开始传入的方法实际是同步直接执行的。它需要一个resolve方法和reject方法作为参数。
4.如果它返回value还是一个Promise,还是直接返回,执行,直到拿到一个非Promise的结果(借助then)。

这里我们需要保证最后把结果传给then的回调方法们,就需要利用一个setTimeout,把这个传值的环节放在事件循环的最后。
resolvedCallbacks和rejectCallbacks实际存储的都是then调用后拿到的回调函数。

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
status = STATUS.PENDING
value = undefined
resolvedCallbacks = []
rejectCallbacks = []
resolve = (value) => {
if (value instanceof MyPromise) {
return value.then(resolve, reject);
}

// 本轮事件循环的末尾
setTimeout(() => {
if (this.state === STATUS.PENDING) {
this.state = STATUS.FULLFIED;
this.value = value;
this.resolvedCallbacks.forEach(callback => {
callback(value);
})
}
})

this.status = STATUS.FULLFIED
}
reject= (value) => {
this.status = STATUS.REJECT
setTimeout(() => {
if (this.state === PENDING) {
this.state = STATUS.REJECT;
this.value = value;
}
this.rejectCallbacks.forEach(callback => {
callback(value);
});
})
}

then函数
1.then返回的就是一个Promise,入参两个,1个是成功的回调,一个是失败的回调。
2.上面说了,它要把自己的方法放进队列里。我们需要实现一个一个接着去执行,所以需要包裹一层。
如果是同步的方法,我们直接执行完拿到结果就好了,如果是异步的,返回的还是一个Promise.then(Promise)去执行。

参考文章

ES6关于Promise的用法
Promise 必知必会(十道题)
javascript中的异步 macrotask 和 microtask 简介
Tasks, microtasks, queues and schedules
Difference between microtask and macrotask within an event loop context

年糕的个人形象照

ABOUT THE GARDENER

年糕

小红书 B站 微信公众号

年糕是一个喜欢把代码、灵感和日常心情慢慢种进花园里的人。会写点前端,做点设计,也认真记录那些看起来不盛大、但会在以后发芽的小事。