Promise的实现及详解

2017-12-10  本文已影响0人  Egde

排版很不友好,抱歉


/*

* 实现Promise是根据Promise规范来的:https://promisesaplus.com/

* 规范很短,所以每句都很重要

* Promise特点

* 1. 状态改变后不可再改变(状态凝固)

*    pending,fullfilled,rejected

*

* 2. then方法(此方法就是最重要的部分)

*    可以链式调用

*    then方法返回一个新的promise实例

*  promise2 = promise.then(f1, f2)

*    当promise的状态确定的时候会执行then中的f1或者f2。

*    在f1/f2执行之前then就已经返回了一个Promise实例(promise2)

*    promise2的状态由f1/f2的返回值确定,以此类推就是链式调用

*

* 3. promise创建时传入的函数会立即执行

*/

/*

* 在考虑某些函数执行时同步还是异步,考虑四种情况,分别会有什么问题

* 假设new一个Promise时传入的函数为funA, then中的函数为funCD

*  1 funA是同步,funCD为同步

*  2 funA是同步,funCD为异步 // 为什么最后只能用这种方式

*  3 funA是异步,funCD为同步

*  4 funA是异步,funCD为异步

*/

/*

* 问题: 1,状态是怎么改变的(这是个好问题,是面试官提的。当时我真的没答出来)

*          在执行resolve,reject函数时会改变状态。看resolve,reject的函数声明

*      2,new Promise时传入的函数为什么要立即执行,then里传的两个函数为什么要异步执行?

*          resolve,reject函数是用户决定什么时候执行的,同步、异步执行都是用户的行为

*          这里可以好好分析下,信息量很大

*/

/*

* 状态确定后挨个调用数组中的方法

* 状态是在resolve,reject函数执行时改变的

*/

function Promise(executor) {

var self = this

/*

* 为什么要创建数组?

* new出第一个实例promise后,可以调用多次then

* 而此时promise的状态可能还不确定,所以来个数组保存下then中的函数,等到状态确定后再执行then中的函数。

* 保存then中的回调函数,注意查看什么时候push进去的

*/

self.resolvedCallbacks = []

self.rejectedCallbacks = []

// 状态

self.status = 'pending'

/*

* resolve中应该包含一个对象,因为每次创建一个实例

* 彼此间都不会有影响(这难道不是单例模式么?还是工厂函数?)

* 状态[确定]后挨个调用数组中的方法,必须被当做函数调用。

* 即调用时函数体内的this应该是undefined/window

* --------------------------------------------------

* 成功传值,失败传原因

* --------------------------------------------------

* resolve和reject是什么时候被执行的呢?

* 这两个函数是在executor体内被执行的

* --------------------------------------------------

* 对于在resolve,reject两个函数中

* 异步执行resolvedCallbacks,rejectedCallbacks中函数问题

*

*/

function resolve(value) {

// 测试用例给我们传了一个我自己定义的Promise实例时

if (value instanceof Promise) {

value.then(resolve, reject)

return

}

// 这里为什么也要异步

// 因为数组resolvedCallbacks,rejectedCallbacks不能跟resolve同步执行。为什么?

setTimeout(function() {

/*

* 这里是为了确保状态不可改变,即resolve/reject只执行一次

*/

if (self.status === 'pending') {

self.status = 'resolved'

// 保存传入的参数,promise成功时的值

self.data = value

var f

// 性能优先,使用原始for循环

for (var i = 0; i < self.resolvedCallbacks.length; i++) {

/*

* 为什么分开写?

* 确保是函数的调用,即被调用时函数体内的this为undefined/window

* 标准里都有说明

*/

f = self.resolvedCallbacks[i]

f(value)

}

}

})

}

function reject(reason) {

// 为什么是异步

setTimeout(function() {

if (self.status === 'pending') {

self.status = 'rejected'

self.data = reason

var f

for (var i = 0; i < self.rejectedCallbacks.length; i++) {

f = self.rejectedCallbacks[i]

f(reason)

}

}

})

}

try {

// promise创建时传入的函数会立即执行,而传入的函数不知道有没有问题.所以要try下

executor(resolve, reject)

} catch(e) {

reject(e)

}

}

/*

* then方法中实现根据状态调用不同的函数,

* 并且在调用回调前返回一个新的promise且此promise的状态由回调函数确定

*  pending -> fulfilled : 调用onResolved

*  pending -> rejected : 调用onRejected

*  onRejected\onResolved必须是函数,否则忽略它

*/

Promise.prototype.then = function(onResolved, onRejected) {

var self = this

if (typeof onResolved !== 'function') {

/*

* 为什么要这样?

* 例如这种情况:p2 = p1.then(null, f2),没有传onResolved函数

*  p1成功了,应该调用onResolved函数。

*  正常情况下p2的状态由onResolved确定,而onResolved没有东西。

*  此时p2的状态应该由p1决定。

*  怎么拿到p1的状态?在then中成功时调用的函数将p1的状态返回就拿到p1的状态!

*  在没有返回函数情况下,将onResolved用一个函数代替

* ---------------------------------------------------------------

*  为什么可以这么写,我没有吃透

*

*  这个函数被执行的时候value就有值了,状态改变onResolved(self.data)执行

*  在这种情况下value的值查查self.data

*  self.data是在构造函数里的resolve(value)里被赋值的。所以这里非常的绕,还需要再来理解消化

*

*/

onResolved = function(value) { return value }

}

if (typeof onRejected !== 'function') {

/*

* 为什么要这样?

* 例如这种情况:p2 = p1.then(null, f2),没有传onResolved函数

*  p1失败,p2也应该失败。

*  这里throw,后面的try可以catch到。catch到就会reject(e)

*  将p1的失败reason抛出来,p2也就失败了

*  具体的过程跟上面的函数是一样的

*/

onRejected = function(reason) { throw reason}

}

/*

* 实例在三种状态下都可以调用then方法

* 判断状态的三种情况。为了方便

* 在三种情况下

* 我要实现的是

*/

var promise2

if (self.status === 'resolved') {

/*

* 返回一个新的promise2,在promise2中调用onResolved且传入promise成功时传入的值

* 而promise成功时传入的值到底是什么,我们是不知道的。

* 这是promise中最复杂的!!!

* ----------------------------------------------------------------------

* onResolved返回值x决定了promise2的状态

*

*/

promise2 = new Promise(function(resolve, reject) {

// 为什么这里要异步?TODO

setTimeout(function() {

try {

/*

* 这个data是第一个promise中传入的函数中的resolve携带的参数!!!!

* onResolved是在then里的,

* 如果onResolved返回的又是Promise实例x呢,

* --------------------------------------------------------------

* 这次的promise的状态是由返回的新的Promise实例决定

* 而新的实例的状态什么时候确定呢?我们不用管,只需要在其后挂个then。

* then中函数运行的时候就是状态确定的时候

* --------------------------------------------------------------

* 直接resolve是不行的,需要在返回的实例x中的then里resolve值

*/

var x = onResolved(self.data)

// 非兼容

// if (x instanceof Promise) {

//  // 非简化。可以想想了,又是递归?

//  // x.then(function(value) {

//  //  resolve(value)

//  // }, function(reason) {

//  //  reject(reason)

//  // })

//  // 简化

/*

* 这种简化让人头疼,resolve,reject是需要传参的。问题来了,两个函数的参数是怎么传进去的

* 这里的resolve,reject其实就是onResolved和onRejected。这种情况又是递归了

*/

//  x.then(resolve, reject)

// } else {

//  resolve(x)

// }

// 兼容下

RESOLVE_PROMISE(promise2, x, resolve, reject)

} catch(e) {

reject(e)

}

})

})

}

if (self.status === 'rejected') {

promise2 = new Promise(function(resolve, reject) {

// 为什么这里要异步?TODO

setTimeout(function() {

try {

var x = onRejected(self.data)

// 非兼容,如果x是Bluebird,Q里的promise呢?if就不会执行了

// if (x instanceof Promise) {

//  x.then(resolve, reject)

// } else {

//  // 为什么是resolve?

//  resolve(x)

// }

// 兼容

RESOLVE_PROMISE(promise2, x, resolve, reject)

} catch(e) {

reject(e)

}

})

})

}

if (self.status === 'pending') {

/*

* promise1还是不确定状态,需要等到promise1确地了状态才能确定promise2应该怎么走

* 查看构造函数里resolve函数的注释,找找灵感。

* -------------------------------------------------------

* 为什么在这就不需要像上面的‘resolved’,‘rejected’里异步呢?

*    因为这里肯定是在将来才执行的

*/

promise2 = new Promise(function(resolve, reject) {

self.resolvedCallbacks.push(function(value) {

try {

var x = onResolved(self.data)

// 非兼容

// if (x instanceof Promise) {

//  x.then(resolve, reject)

// } else {

//  resolve(x)

// }

// 兼容

RESOLVE_PROMISE(promise2, x, resolve, reject)

} catch(e) {

reject(e)

}

})

self.rejectedCallbacks.push(function(reason) {

try {

var x = onRejected(self.data)

// 非兼容

// if (x instanceof Promise) {

//  x.then(resolve, reject)

// } else {

//  resolve(x)

// }

// 兼容

RESOLVE_PROMISE(promise2, x, resolve, reject)

} catch(e) {

reject(e)

}

})

})

}

return promise2

}

/*

* 上面就基本已经实现了Promise,但是没有达到 https://promisesaplus.com/ 规范的要求。

* 这个函数是多种实现中的一种

* 这个函数的作用是使其他的Promise可以互相兼容------兼容

* ------------------------------------------------------------------------

* 各参数说明

*    promise:

*    x:

*    resolve:

*    reject:

*/

function RESOLVE_PROMISE(promise, x, resolve, reject) {

if (promise === x) {

// 一个promise实例resolve了它自己

reject(new TypeError('Chaining cycle is deteced for promise'))

return

}

// 如果x是自己的Promise的实现

if (x instanceof Promise) {

if (x.status === 'pending') {

x.then(function(v) {

// god,又是递归

RESOLVE_PROMISE(promise, v, resolve, reject)

}, reject)

} else {

x.then(resolve, reject)

}

return

}

// 只有对象和函数这两种数据类型可以保存属性,然而typeof null也是'object'

if (x !== null && (typeof x === 'object' || typeof x === 'function')) {

try {

/*

* 为什么要把then单独拿出来?避免副作用

*  因为我们是在读别人给的属性,但是我们不知道该属性具体是什么。也可能报错

*  如果该属性是getter,则每次调用就会返回不同的值。

* 所以只调用一次

* ---------------------------------------------------------------------

* a,b,c三者只能调用一个,为什么?TODO

*  避免原型链上有then函数(thenable)但却不是Promise的实现,但是为什么这样就可以避免,我并没有吃透

* ---------------------------------------------------------------------

* if语句里有两个可以可疑的函数,有函数名resolvePromise,rejectPromise。

* 既然是当参数传,我觉得不需要函数名应该也一样吧

*/

var then = x.then

var called = false // 记录执行状态

if (typeof then === 'function') {

then.call(x, function resolvePromise(y) {

if (called) {

return

}

called = true

// 产生了递归

RESOLVE_PROMISE(promise, y, resolve, reject)//--- a

}, function rejectPromise(r) {

if (called) {

return

}

called = true

reject(r) //------------------------------------- b

})

} else {

resolve(x)

}

} catch(e) {

if (called) {

return

}

called =true

reject(e) //----------------------------------------- c

}

} else {

resolve(x)

}

}

上一篇下一篇

猜你喜欢

热点阅读