目标:
- 了解Promise特点
- 掌握Promise的定义和用法
- 通过手写Promise进一步加深理解
1、Promise的基本功能
基本写法及定义:
let P1 = new Promise((resolve, reject) => {
resolve('success')
rejected('fail')
})
console.log('P1', P1)
let P2 = new Promise((resolve, reject) => {
reject('fail')
resolve('success')
})
console.log('P2', P2)
let P3 = new Promise((resolve, reject) => {
throw('error')
})
console.log('P3', P3)
执行上面的代码之后执行结果如下所示:
首先先了解Promise
最基本的几个特点:
Promise
有三个状态,分别是pending
、fulfilled
、rejected
- 执行了
resolve
之后,Promise
的状态变为fulfilled
; - 执行了
rejecte
之后,Promise
的状态变为rejected
; - 在执行上面两个方法之前,
Promise
的状态为pending
- 执行了
Promise
的状态一旦改变之后就不可再变- 遇到
throw
会抛出异常,相当于执行了reject
现在来手写下具备这些特点的 Promise
class MyPromise {
constructor(exector) {
this.initValue() //初始化要用到的参数
this.initBind() //改变传入的resolve和reject的指针指向
// 通过try和catch实现遇到throw的时候执行reject方法的功能
try {
exector(this.resolve, this.reject)
} catch(err) {
this.reject(err)
}
}
initValue() {
this.PromiseState = 'pending'
this.PromiseResult = null
}
initBind() {
this.resolve = this.resolve.bind(this)
this.reject = this.reject.bind(this)
}
resolve(result) {
if(this.PromiseState != 'pending') return //保证状态不可逆
this.PromiseState = 'fulfilled'
this.PromiseResult = result
}
reject(reason) {
if(this.PromiseState != 'pending') return //保证状态不可逆
this.PromiseState = 'rejected'
this.PromiseResult = reason
}
}
2、then
我们平时在业务中用 Promise
用到的最多的就是 then
方法了,它的用法如下:
// 马上输出 ”success“
const p1 = new Promise((resolve, reject) => {
resolve('success')
}).then(res => console.log(res), err => console.log(err))
// 1秒后输出 ”fail“
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('fail')
}, 1000)
}).then(res => console.log(res), err => console.log(err))
// 链式调用 输出 200
const p3 = new Promise((resolve, reject) => {
resolve(100)
}).then(res => 2 * res, err => console.log(err))
.then(res => console.log(res), err => console.log(err))
在这里我们可以确定它又有以下这些特点:
then
接收两个回调参数,一个是成功回调,一个是失败回调;- 当
Promise
状态为fulfilled
执行成功回调,为rejected
执行失败回调; - 如
resolve
或reject
在定时器里,则定时器结束后再执行then
; then
支持链式调用,下一次then
执行受上一次then
返回值的影响;
2.1、先来实现then方法
class MyPromise {
constructor(exector) {
this.initValue()
this.initBind()
try {
exector(this.resolve, this.reject)
} catch(err) {
this.reject(err)
}
}
initValue() {
this.PromiseState = 'pending'
this.PromiseResult = null
}
initBind() {
this.resolve = this.resolve.bind(this)
this.reject = this.reject.bind(this)
}
resolve(result) {
if(this.PromiseState != 'pending') return
this.PromiseState = 'fulfilled'
this.PromiseResult = result
}
reject(reason) {
if(this.PromiseState != 'pending') return
this.PromiseState = 'rejected'
this.PromiseResult = reason
}
then(onFulfilled, onRejected) {
// 判断参数类型,保证回调是函数类型
onFulfilled = typeof onFulfilled == 'function' ? onFulfilled : val => val //成功回调是值的时候直接返回该值,供下一次链式调用时使用
onRejected = typeof onRejected == 'function' ? onRejected : reason => { throw reason } //失败回调传值的时候抛出错误为该值的异常
// 对应的状态下执行对应的回调方法
if(this.PromiseState == 'fulfilled') onFulfilled(this.PromiseResult)
if(this.PromiseState == 'rejected') onRejected(this.PromiseResult)
}
}
但是这段代码只是实现了前两个的的基本特点, timeout
和链式调用是不可用的。
2.2、timeout模拟异步情况
关键问题是解决如何在一秒后再执行 then
里面的回调。我们可以把 then
里面的两个回调保存起来,等执行完 resolve
或者 reject
之后再去判断状态决定执行哪个回调。回调通过数组来保存,以解决后续的链式调用的问题。
class MyPromise {
constructor(exector) {
this.initValue()
this.initBind()
try {
exector(this.resolve, this.reject)
} catch(err) {
this.reject(err)
}
}
initValue() {
this.PromiseState = 'pending'
this.PromiseRsult = null
this.fulfilledCallbacks = []
this.rejectedCallbacks = []
}
initBind() {
this.resolve = this.resolve.bind(this)
this.reject = this.reject.bind(this)
}
resolve(result) {
if(this.PromiseState != 'pending') return
this.PromiseState = 'fulfilled'
this.PromiseResult = result
while(this.fulfilledCallbacks.length) {
const x = this.fulfilledCallbacks.shift()
x(this.PromiseResult)
}
}
reject(reason) {
if(this.PromiseState != 'pending') return
this.PromiseState = 'rejected'
this.PromiseResult = reason
while(this.rejectedCallbacks.length) {
const x = this.rejectedCallbacks.shift()
x(this.PromiseResult)
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled == 'function' ? onFulfilled : val => val
onRejected = typeof onRejected == 'function' ? onRejected : reason => { throw reason }
if(this.PromiseState == 'fulfilled') onFulfilled(this.PromiseResult)
else if(this.PromiseState == 'rejected') onRejected(this.PromiseResult)
else if(this.PromiseState == 'pending') {
this.fulfilledCallbacks.push(onFulfilled.bind(this)) //需要将传入的回调绑定到该实例上,原理同initBind()里的bind
this.rejectedCallbacks.push(onRejected.bind(this)) //同上
}
}
}
2.3、链式调用
then
支持链式调用,下一次 then
执行受上一次 then
返回值的影响,如:
// 链式调用 输出 200
const p1 = new Promise((resolve, reject) => {
resolve(100)
}).then(res => 2 * res, err => console.log(err))
.then(res => console.log(res), err => console.log(err))
// 链式调用 输出300
const p2 = new Promise((resolve, reject) => {
resolve(100)
}).then(res => new Promise((resolve, reject) => resolve(3 * res)), err => console.log(err))
.then(res => console.log(res), err => console.log(err))
可以得出链式调用有以下特点:
then
方法本身会返回一个新的Promise
对象- 如果返回值非
Promise
对象,新Promise
对象就是成功,值为此返回值 - 如果返回值是
Promise
对象,新Promise
对象的结果跟着返回值的Promise
的结果走
class MyPromise {
constructor(exector) {
this.initValue()
this.initBind()
try {
exector(this.resolve, this.reject)
} catch(err) {
this.reject(err)
}
}
initValue() {
this.PromiseState = 'pending'
this.PromiseRsult = null
this.fulfilledCallbacks = []
this.rejectedCallbacks = []
}
initBind() {
this.resolve = this.resolve.bind(this)
this.reject = this.reject.bind(this)
}
resolve(result) {
if(this.PromiseState != 'pending') return
this.PromiseState = 'fulfilled'
this.PromiseResult = result
while(this.fulfilledCallbacks.length) {
this.fulfilledCallbacks.shift()(this.PromiseResult)
}
}
reject(reason) {
if(this.PromiseState != 'pending') return
this.PromiseState = 'rejected'
this.PromiseResult = reason
while(this.rejectedCallbacks.length) {
this.rejectedCallbacks.shift()(this.PromiseResult)
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled == 'function' ? onFulfilled : val => val
onRejected = typeof onRejected == 'function' ? onRejected : reason => { throw reason }
var thenPromise = new MyPromise((resolve, reject) => {
const resolvePromise = cb => {
try {
const x = cb(this.PromiseResult)
if(x == thenPromise) {
throw new Error('不能返回自身')
}
if(x instanceof MyPromise) {
x.then(resolve, reject)
} else {
resolve(x)
}
} catch(err) {
reject(err)
throw(err)
}
}
if(this.PromiseState == 'fulfilled') resolvePromise(onFulfilled)
else if(this.PromiseState == 'rejected') resolvePromise(onRejected)
else if(this.PromiseState == 'pending') {
this.fulfilledCallbacks.push(resolvePromise.bind(this, onFulfilled))
this.rejectedCallbacks.push(resolvePromise.bind(this, onRejected))
}
})
return thenPromise
}
}
3、补充知识点
3.1、setTimeout
setTimeout
是单线程,类似异步,但不是异步
console.log(1)
setTimeout(() => {
console.log(2);
}, 2000)
console.log(3)
setTimeout(() => {
console.log(4)
})
setTimeout(() => {
console.log(5);
}, 0)
setTimeout(() => {
console.log(6)
})
console.log(7)
setTimeout(() => {
console.log(8);
}, 1000)
/*
控制台依次输出以下结果
1
3
7
4
5
6
8
2
*/
可以看出,setTimeout
并没有阻塞主线程,因此 setTimeout
明显不是同步执行。但是虽然设置了第二个 setTimeout
的时间为0,但是仍是在输出1、3、7后执行的。所以也明显不是异步。
首先,要先清楚JavaScript中的任务队列这一概念。在JavaScript中,任务分为两种:异步任务和同步任务。
- 同步任务:在主线程上排队执行的任务,只有在前一个任务执行完毕后,才能继续执行下一个任务
- 异步任务:不进入主线程,而是进入任务队列的的任务。等主线程的任务全部执行完毕之后,主线程会通过event loop(事件循环)去询问任务队列中是否有可执行的任务,有则进入主线程执行。
setTimeout
就是被放入异步任务队列中,等待主线程的任务执行完毕后再到主线程中执行。因此 setTimeout
的异步只是假象。