手写promsie
我们首先来看两个基于promise封装的例子。
基于Promsie封装的图片加载函数
来看一个简单的基于Promsie封装的图片加载函数
function loadImageAsync(url) {
return new Promise((resolve,reject) => {
// 创建图片对象
const image = new Image();
image.src = url;
image.onLoad = function() {
// 返回image对象
resolve(image);
}
image.onerror = function() {
reject(new Error('Could not load image at ' + url));
}
})
}
// 封装的函数返回一个Promsie包含then方法,结果
loadImageAsync("./loadImge.jpg").then(image => document.body.appendChild(image))
基于Promise对象实现Ajax操作的例子
const getJSON = function(url) {
const promsie = new Promsie((resolve, reject) => {
const handle = function() {
if (this.readyState !== 4) {
return;
}
if (this.status === 200) {
resolve(this.response)
} else {
reject(new Error(this.statusText)
}
}
const client = new XMLHttpRequest()
client.open("GET",url)
client.onreadystatechange = handler;
client.responseType = "json";
client.setRequestHeader("Accept", "application/json");
client.send();
})
return promsie
}
getJSON("/posts.json").then(function(json){
console.log('Contents: ' + json);
},function(error) {
console.error('出错了', error);
})
来看一下promise的简单示例。
new Promise(()=>{
console.log("executor")
})
console.log("ok")
// 控制行输出 => executor ok
-
Promsie可以使用new关键词调用,我们可以把Promise看成是一个类,在现阶段我们不考虑兼容性问题。
-
当使用Promise的时候,会传入一个
executor函数,这个函数会立即执行。 -
这个executor函数可以传递两个参数,这两个参数也是函数,一个是resolve 另一个是reject,这两个函数的执行可以改变promise的状态。
-
promise中有三种状态:
- 成功态
- 失败态
- 中间状态:pending
-
默认情况下promsie处于中间状态。
-
使用new操作符操作promsie之后,会生成一个实例,每一个实例都拥有一个then方法。
-
then方法接收两个函数作为参数,一个是成功回调,一个是失败回调。根据上面的描述信息,我们补充代码:
let promise = new Promise((resolve, reject) => {
console.log("executor")
})
console.log("ok")
// 每个实例都用拥有一个then方法
promise.then(()=>{
console.log("success");
},()=>{
console.log("failed");
})
// 控制行输出 => executor ok
通过打印输出可以看到,虽然我们在then方法中写了success 和 failed 但是既没有走成功回调,也没有走失败回调。
这是因为默认状态下promsie处于的是等待状态。我们想要成功还是失败,需要主动调用resolve或者调用reject告诉promsie。如果调用resolve函数promise转变为成功状态,如果调用reject函数promise就转变为失败状态。
第一版代码
我们尝试手写一版Promise,使用 commonjs 规范,之后使用Promsie的时候就导入自己手写的这个 Promise 类。
// promsie 有三种状态,分别是 等待、成功、失败 我们用常量来表示。
const PENDING = "PENDING"
const FULFILLED = "FULFILLED"
const REJECTED = "REJECTED"
// Promsie 内部是一个类,我们这里使用 class 来实现
class Promise {
// 构造函数的入参就是自执行函数 executor
constructor(executor) {
this.status = PENDING // promise默认的状态
this.value = undefined // 成功的值
this.reason = undefined // 失败的原因
// 成功resolve函数
const resolve = (value) => {
// 只有在 PENDING 状态下 才能 修改状态
if (this.status === PENDING) {
this.value = value
this.status = FULFILLED // 修改状态
}
}
// 失败的reject函数
const reject = (reason) => {
// 只有在 PENDING 状态下 才能 修改状态
if (this.status === PENDING) {
this.reason = reason
this.status = REJECTED // 修改状态
}
}
// 这里使用try catch包裹是因为立即执行函数执行的时候如果抛出异常,直接走reject函数。
try {
executor(resolve, reject)
} catch (e) {
reject(e)
}
}
// then方法的两个回调函数,都是使用者传递的
then(onFulfilled, onRejected) {
// onFulfilled, onRejected
if (this.status == FULFILLED) {
// 成功调用成功方法,并传入成功的值
onFulfilled(this.value)
}
if (this.status === REJECTED) {
// 失败调用失败方法 并传入失败的原因
onRejected(this.reason)
}
}
}
module.exports = Promise
我们在实际的使用场景中,一般在executor函数中传入的都是异步任务,类似于下面这样。
// 使用自己的promsie
let Promise = require('./promise')
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("executor")
}, 1000)
})
console.log("ok")
promise.then(()=>{
console.log("success");
},()=>{
console.log("failed");
})
// => 控制台打印 ok
执行上述代码发现,如果我们在executor函数中传入的是一个异步任务,虽然调用了resolve方法,但是并没有走实例的then方法的成功回调,这是为什么呢?
因为默认promise处于pending状态,当我们用定时器延时去触发resolve的时候,下面的then方法已经执行完毕了,但是我们自己手写的这一版本promsie中并没有处理pending状态。
处理pending状态
我们可以这样处理,准备两个数组,分别用于存放成功回调函数和失败回调函数,当我们真正触发 resolve 或者 reject 的时候遍历数组执行其中的回调函数。
这种实现的思路其实是发布订阅的模式。先订阅好所有的成功回调和失败回调,然后在触发 resolve 和 reject 的时候执行发布。
// promsie 有三种状态,分别是 等待 成功 失败 我们用常量来表示。
const PENDING = "PENDING"
const FULFILLED = "FULFILLED"
const REJECTED = "REJECTED"
// Promsie 内部是一个类,我们这里使用 class 来实现
class Promise {
// 构造函数的入参就是自执行函数 executor
constructor(executor) {
this.status = PENDING // promise默认的状态
this.value = undefined // 成功的原因
this.reason = undefined // 失败的原因
this.onResolvedCallbacks = []; // 存放成功的回调方法
this.onRejectedCallbacks = []; // 存放失败的回调方法
// 成功resolve函数
const resolve = (value) => {
// 只有在 PENDING 状态下 才能 修改状态
if (this.status === PENDING) {
this.value = value
this.status = FULFILLED // 修改状态
this.onResolveCallbacks.forEach((fn) => fn())
}
}
// 失败的reject函数
const reject = (reason) => {
if (this.status === PENDING) {
this.reason = reason
this.status = REJECTED // 修改状态
this.onRejecteCallbacks.forEach((fn) => fn())
}
}
try {
executor(resolve, reject)
} catch (e) {
// 立即执行函数执行的时候如果抛出异常,直接走reject函数。
reject(e)
}
}
then(onFulfilled, onRejected) {
if(this.status == PENDING){
this.onResolvedCallbacks.push(() => {
// todo...
onFulfilled(this.value);
});
this.onRejectedCallbacks.push(() => {
// todo...
onRejected(this.reason);
});
}
// onFulfilled, onRejected
if (this.status == FULFILLED) {
// 成功调用成功方法,并传入成功的值
onFulfilled(this.value)
}
if (this.status === REJECTED) {
// 失败调用失败方法 并传入失败的原因
onRejected(this.reason)
}
}
}
module.exports = Promise
promsie 到底帮助我们解决了什么问题
promsie 到底在我们的日常开发中解决了什么问题?
- 解决了回调地域的问题。
- 解决了同步并发的问题。
假设我们有一个场景:存在两个文件 a.txt、b.txt。
a.txt中的内容是一个字符串'b.txt', b.txt中的内容是 'b',我们希望先从 a.txt 中读取文件内容,然后再操作输出b.txt的内容。我们在不使用promsie的时候可能会写出如下的代码:
fs.readFile('./a.txt','utf8',function (err, data) {
if(err) return console.log(err)
fs.readFile('./b.txt','utf8',(err,data) => {
if(err) return console.log(err)
console.log(data); // => 输出b
})
})
这种写法会有回调嵌套的问题,就是不断地在上一个函数的回调中写逻辑,并且每一层都要写错误捕获逻辑。这样层级多了之后,代码就会变得难以维护。
基于此我们可以尝试使用promise封装一个方法。
const fs = require('fs');
// 入参是文件的 路径 和 文件编码
function readFile(filePath, encoding) {
// 返回一个 promsie实例
return new Promise((resolve,reject) => {
// 传递给原生的fs.readFile 方法一个回调函数
fs.readFile(filePath,encoding, (err,data) => {
// 拿到文件读取结果之后,通过resolve 或者 reject 将结果返回。
if(err) return reject(err);
resolve(data);
})
})
}
如上述代码所示,我们封装了一个 readFile 方法,返回了一个 promise 实例,在自执行函数中调用原生的操作文件的方法,在文件读取结束之后,将结果通过resove 或者 reject 返回出来。
promise中给我们提供的then方法能够解决这种不断嵌套的问题。
then方法
promsie的链式调用是个很有意思的现象,当promise实例调用then方法后,还会返回一个新的promise,新的promsie拥有有自己的状态,如果我们用过jquery这个库的时候会知道,也会有链式调用的场景,但是返回的是this。
那我们不禁有一个疑问,promsie为什么不能返回自身呢?原因就在于,promise的状态一旦改变之后,就不能再改回去了,否则状态就乱了,因此,需要返回一个新的promsie。
情况1:then中的方法返回值是一个普通值
所谓的普通值,代表不是promise实例的场景,比如基本类型的数字或者字符串。这种情况下,返回值会作为外层下一次then的成功结果。
还是拿刚才我们基于promsie封装的函数readFile来说
readFile("./a.txt", "utf8").then((value)=>{
reutrn 1
},(err)=>{
console.log('fail',err)
}).then((data)=>{
console.log('s',data) // 在这里输出 => s 1
},(err)=>{
console.log('f',err)
})
情况2:then方法执行出错
比如 throw new Error("失败") 无论上一次是成功还是失败,只要返回的是普通值,都会执行下一次的then的成功。
readFile("./a.txt", "utf8").then((value)=>{
throw new Error("抛出异常")
},(err) => {
console.log('fail',err) // 在这里输出 抛出异常
}).then((data) => {
console.log('s',data) // 同时默认在上层返回undefined => s undefined
},(err)=>{
console.log('f',err)
})
上面的这个例子中,虽然在第一个then中抛出异常,但是没有显示的返回数据,其实就是返回undefined, 会作为普通值传递给下一个then。
情况3:then中方法返回的是一个promsie对象
此时会根据promsie的结果来处理是走成功还是失败。
readFile("./a.txt", "utf8").then((value)=>{
return readFile('b.txt') // 这个执行的结果是 b
},(err) => {
console.log('fail',err)
}).then((data) => {
console.log('s',data) // => s b
},(err)=>{
console.log('f',err)
})
在第{2}行代码中, readFile('b.txt') 这个函数的执行结结果是返回一个b,就可以在下一个then的成功回调中拿到这个值。反之,如果readFile('b.txt')返回的是一个失败的结果,那么就会走到第二个then的失败回调。
readFile("./a.txt", "utf8").then((value)=>{
return readFile('b1.txt') // b1.txt 并不存在
},(err) => {
console.log('fail',err)
}).then((data) => {
console.log('s',data)
},(err)=>{
console.log('f',err) // => 走到这里
})
总结一下:
then方法如果返回一个普通值(除了promsie)就会传递给下一个then的成功。如果返回一个失败的promsie或者抛出异常,会走下一个then的失败。
let promise2 = new Promise((resolve, reject) => {
resolve('成功')
}).then((data) => {
console.log(data) // => ‘成功’
return 'x'
})
promise2.then((data) => {
console.log("success", data) // => success x
},(err) => {
console.log("failed", err)
}
)
从上面代码我们已经知道,每调用then方法一次,都会产生一个新的promsie,并且第一次then的时候,无论是成功和是失败,都会有隐含的返回值,如果不指定就是返回undefined,那这个返回值,是能够成为外面then的入参继续传递下去。
那如果在第一个then方法的成功回调中抛出异常,函数该如何执行呢?
let promise2 = new Promise((resolve, reject) => {
resolve('成功')
}).then((data) => {
console.log(data) // => 成功
throw new Error("抛出异常")
})
promise2.then((data) => {
console.log("success", data)
},(err) => {
console.log("failed", err) // => failed errorxxxx
}
)
如果在第一个promise的成功回调函数中, 抛出异常,会在第二个then的方法中的失败回调中得到结果。这很显然是调用了第二个promsie的resolve或者reject方法,根据此,我们需要同步捕获错误异常。
第二版代码
// promsie 有三种状态,分别是 等待 成功 失败 我们用常量来表示。
const PENDING = "PENDING"
const FULFILLED = "FULFILLED"
const REJECTED = "REJECTED"
// Promsie 内部是一个类,我们这里使用 class 来实现
class Promise {
// 构造函数的入参就是自执行函数 executor
constructor(executor) {
this.status = PENDING // promise默认的状态
this.value = undefined // 成功的原因
this.reason = undefined // 失败的原因
this.onResolvedCallbacks = []; // 存放成功的回调方法
this.onRejectedCallbacks = []; // 存放失败的回调方法
// 成功resolve函数
const resolve = (value) => {
// 只有在 PENDING 状态下 才能 修改状态
if (this.status === PENDING) {
this.value = value
this.status = FULFILLED // 修改状态
this.onResolveCallbacks.forEach((fn) => fn())
}
}
// 失败的reject函数
const reject = (reason) => {
if (this.status === PENDING) {
this.reason = reason
this.status = REJECTED // 修改状态
this.onRejecteCallbacks.forEach((fn) => fn())
}
}
try {
executor(resolve, reject)
} catch (e) {
// 立即执行函数执行的时候如果抛出异常,直接走reject函数。
reject(e)
}
}
then(onFulfilled, onRejected) {
// 每次then的时候 都会产生一个新的promsie,我们将这个新的promsie
// 命名为Promsie2。又因为promise接收的是一个立即执行函数,并不会影响结果。
let promsie2 = new Promise((resolve, reject) => {
if(this.status == PENDING) {
this.onResolvedCallbacks.push(() => {
// 这里就是用来同步捕获异常场景的
try {
// todo...
let x = onFulfilled(this.value);
resolve(x)
catch(e) {
reject(e)
}
});
this.onRejectedCallbacks.push(() => {
try {
// todo...
let x = onRejected(this.reason);
resolve(x)
catch(e) {
reject(e)
}
});
}
// onFulfilled, onRejected
if (this.status == FULFILLED) {
try {
// 成功调用成功方法,并传入成功的值
let x = onFulfilled(this.value)
resolve(x)
} catch(e) {
reject(e)
}
}
if (this.status === REJECTED) {
try {
// 失败调用失败方法 并传入失败的原因
let x = onRejected(this.reason)
resolve(x)
} catch(e) {
reject(e)
}
}
})
return promsie2
}
}
module.exports = Promise
在promsieA+ 规范中有说过这样的点, onFulfilled 和 onRejected 不能在当前上下文中调用,这里我就必须想办法,让这两个函数异步触发,异步触发,这里我们使用setTimeout来实现,原因是我们没有办法模拟浏览器的微任务处理逻辑。
第三版代码
// promsie 有三种状态,分别是 等待 成功 失败 我们用常量来表示。
const PENDING = "PENDING"
const FULFILLED = "FULFILLED"
const REJECTED = "REJECTED"
// Promsie 内部是一个类,我们这里使用 class 来实现
class Promise {
// 构造函数的入参就是自执行函数 executor
constructor(executor) {
this.status = PENDING // promise默认的状态
this.value = undefined // 成功的原因
this.reason = undefined // 失败的原因
this.onResolvedCallbacks = []; // 存放成功的回调方法
this.onRejectedCallbacks = []; // 存放失败的回调方法
// 成功resolve函数
const resolve = (value) => {
// 只有在 PENDING 状态下 才能 修改状态
if (this.status === PENDING) {
this.value = value
this.status = FULFILLED // 修改状态
this.onResolveCallbacks.forEach((fn) => fn())
}
}
// 失败的reject函数
const reject = (reason) => {
if (this.status === PENDING) {
this.reason = reason
this.status = REJECTED // 修改状态
this.onRejecteCallbacks.forEach((fn) => fn())
}
}
try {
executor(resolve, reject)
} catch (e) {
// 立即执行函数执行的时候如果抛出异常,直接走reject函数。
reject(e)
}
}
then(onFulfilled, onRejected) {
// 每次then的时候 都会产生一个新的promsie,我们将这个新的promsie
// 命名为Promsie2。又因为promise接收的是一个立即执行函数,并不会影响结果。
let promsie2 = new Promise((resolve, reject) => {
if(this.status == PENDING) {
this.onResolvedCallbacks.push(() => {
setTimeout(() => {
try {
// todo...
let x = onFulfilled(this.value);
resolve(x)
} catch(e) {
reject(e)
}
},0)
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
// todo...
let x = onRejected(this.reason);
resolve(x)
} catch(e) {
reject(e)
}
},0)
});
}
// onFulfilled, onRejected
if (this.status == FULFILLED) {
setTimeout(() => {
try {
// 成功调用成功方法,并传入成功的值
let x = onFulfilled(this.value)
resolve(x)
} catch(e) {
reject(e)
}
},0)
}
if (this.status === REJECTED) {
setTimeout(() => {
try {
// 失败调用失败方法 并传入失败的原因
let x = onRejected(this.reason)
resolve(x)
} catch(e) {
reject(e)
}
},0)
}
})
return promsie2
}
}
module.exports = Promise
在第一个promise then 方法中很可能返回的还是一个promsie, 也就是说 onFulfilled 执行完的x可能是一个Promise。类似于下面的代码:
let promise2 = new Promise((resolve) => {
resolve(1)
}).then((data) => {
// onFulfilled 函数的返回值x 可能还是个 Promsie
return new Promise((resolve, reject) => {
// x 可能是一个promise
setTimeout(() => {
resolve("ok")
}, 1000)
})
},(err) => {
return 111
}
)
promise2.then((data) => {
console.log(data) // ok
},(err) => {
console.log("error", err)
}
)
我们可以看到,第二个then的成功回调中拿到的是新的创建出来的promise的结果。根据上面的描述,我们需要专门创建一个处理函数来针对x的不同取值做相应的操作,这个方法是相对独立的。
第四版代码
// promsie 有三种状态,分别是 等待 成功 失败 我们用常量来表示。
const PENDING = "PENDING"
const FULFILLED = "FULFILLED"
const REJECTED = "REJECTED"
// Promsie 内部是一个类,我们这里使用 class 来实现
class Promise {
// 构造函数的入参就是自执行函数 executor
constructor(executor) {
this.status = PENDING // promise默认的状态
this.value = undefined // 成功的原因
this.reason = undefined // 失败的原因
this.onResolvedCallbacks = []; // 存放成功的回调方法
this.onRejectedCallbacks = []; // 存放失败的回调方法
// 成功resolve函数
const resolve = (value) => {
// 只有在 PENDING 状态下 才能 修改状态
if (this.status === PENDING) {
this.value = value
this.status = FULFILLED // 修改状态
this.onResolveCallbacks.forEach((fn) => fn())
}
}
// 失败的reject函数
const reject = (reason) => {
if (this.status === PENDING) {
this.reason = reason
this.status = REJECTED // 修改状态
this.onRejecteCallbacks.forEach((fn) => fn())
}
}
try {
executor(resolve, reject)
} catch (e) {
// 立即执行函数执行的时候如果抛出异常,直接走reject函数。
reject(e)
}
}
then(onFulfilled, onRejected) {
// 每次then的时候 都会产生一个新的promsie,我们将这个新的promsie
// 命名为Promsie2。又因为promise接收的是一个立即执行函数,并不会影响结果。
let promsie2 = new Promise((resolve, reject) => {
if(this.status == PENDING) {
this.onResolvedCallbacks.push(() => {
setTimeout(() => {
try {
// todo...
let x = onFulfilled(this.value);
resolvePromise(promsie2, x, resolve, reject)
} catch(e) {
reject(e)
}
},0)
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
// todo...
let x = onRejected(this.reason);
resolvePromise(promsie2, x, resolve, reject)
} catch(e) {
reject(e)
}
},0)
});
}
// onFulfilled, onRejected
if (this.status == FULFILLED) {
setTimeout(() => {
try {
// 成功调用成功方法,并传入成功的值
let x = onFulfilled(this.value)
resolvePromise(promsie2, x, resolve, reject)
} catch(e) {
reject(e)
}
},0)
}
if (this.status === REJECTED) {
setTimeout(() => {
try {
// 失败调用失败方法 并传入失败的原因
let x = onRejected(this.reason)
resolvePromise(promsie2, x, resolve, reject)
} catch(e) {
reject(e)
}
},0)
}
})
return promsie2
}
}
module.exports = Promise
resolvePromise函数处理。
应该遵循以下原则:
- 1 如果promsie2和x是同一个promsie,应该抛出异常。
function resolvePromise(promsie2, x, resolve, reject) {
// 核心流程
if (promsie2 === x) {
return reject(new TypeError("错误"))
}
}
- 2 我们还需要考虑兼容别人的Promise 这部分逻辑稍微复杂一些。首先promsie是一个对象或者函数,如果是普通值直接返回就好,还需要明确Promise中是必须拥有then方法的,在获取then方法的时候可能会抛出异常,因此这部分需要做异常捕获操作。
function resolvePromise(promsie2, x, resolve, reject) {
// 核心流程
if (promsie2 === x) {
return reject(new TypeError("错误"))
}
// 我可能写的promise 要和别人的promise兼容,考虑不是自己写的promise情况
if ((typeof x === "object" && x !== null) || typeof x === "function") {
// 有可能是promise 还必须拥有then方法,在取 then的时候,可能会抛出异常。
// 这里应该捕获异常
try {
let then = x.then
}catch(e) {
reject(e) // 如果捕获到了异常,将异常直接传递给 reject 函数
}
} else {
resolve(x) // 说明返回的是一个普通值 直接将他放到 promise2.resolve中。
}
}
- 3 接下来,我们还需要判断 then 是不是一个函数,如果then 是一个函数,那么就要绑定this指向。如果不是一个函数,就是一种普通的值的处理逻辑。
- 4 别人的promsie可能会有致命的错误,就是调取完resolve之后,还可能调取reject逻辑。所以需要设置一个锁来保证调用的结果正确。
function resolvePromise(promsie2, x, resolve, reject) {
// 核心流程
if (promsie2 === x) {
return reject(new TypeError("错误"))
}
// 我可能写的promise 要和别人的promise兼容,考虑不是自己写的promise情况
if ((typeof x === "object" && x !== null) || typeof x === "function") {
// 有可能是promise 还必须拥有then方法,在取 then的时候,可能会抛出异常。
let called = false
try {
let then = x.then
if (typeof then === 'function') {
// 这里就认为是Promise了 这里面不 用 x.then的愿因是 可能还会触发自定义的getter
// 如果成功就将成功的结果给promise2的resolve方法,如果失败, 就将失败的原因传递给reject方法
// 这里只是稍微改变了 then的this指向。
then.call(x, (y) => {
if (called) return
called = true
resolve(y)
},(r) => {
if (called) return
called = true
reject(r)
})
} else { // then 是一个对象 直接使用resolve 函数返回出去
resolve(x)
}
}catch(e) {
if (called) return
called = true
reject(e) // 如果捕获到了异常,将异常直接传递给 reject 函数
}
} else {
resolve(x) // 说明返回的是一个普通值 直接将他放到 promise2.resolve中。
}
}
- 5 还有一种嵌套调用promise的情况,这种情况需要做一个递归调用。
function resolvePromise(promsie2, x, resolve, reject) {
// 核心流程
if (promsie2 === x) {
return reject(new TypeError("错误"))
}
// 我可能写的promise 要和别人的promise兼容,考虑不是自己写的promise情况
if ((typeof x === "object" && x !== null) || typeof x === "function") {
// 有可能是promise 还必须拥有then方法,在取 then的时候,可能会抛出异常。
let called = false
try {
let then = x.then
if (typeof then === 'function') {
// 这里就认为是Promise了 这里面不 用 x.then的愿因是 可能还会触发自定义的getter
// 如果成功就将成功的结果给promise2的resolve方法,如果失败, 就将失败的原因传递给reject方法
// 这里只是稍微改变了 then的this指向。
then.call(x, (y) => {
if (called) return
called = true
resolvePromise(promsie2, y, resolve, reject)
},(r) => {
if (called) return
called = true
reject(r)
})
} else { // then 是一个对象 直接使用resolve 函数返回出去
resolve(x)
}
} catch(e) {
if (called) return
called = true
reject(e) // 如果捕获到了异常,将异常直接传递给 reject 函数
}
} else {
resolve(x) // 说明返回的是一个普通值 直接将他放到 promise2.resolve中。
}
}
onFulfilled, onRejected 在使用then的时候很可能是不传递的。此时需要针对这种情况单独做处理。做一层判断。贴出完整实现
第五版代码
const PENDING = "PENDING"
const FULFILLED = "FULFILLED"
const REJECTED = "REJECTED"
// 利用x的值来判断是调用promise2的resolve还是reject
function resolvePromise(promise2, x, resolve, reject) {
// 核心流程
if (promise2 === x) {
return reject(new TypeError("错误"))
}
// 我可能写的promise 要和别人的promise兼容,考虑不是自己写的promise情况
if ((typeof x === "object" && x !== null) || typeof x === "function") {
// 有可能是promise
// 别人的promise可能调用成功后 还能调用失败~~~ 确保了别人promise符合规范
let called = false
try {
// 有可能then方法是通过defineProperty来实现的 取值时可能会发生异常
let then = x.then
if (typeof then === "function") {
// 这里我就认为你是promise了 x.then 这样写会触发getter可能会发生异常
then.call(
x,
(y) => {
if (called) return
called = true
resolvePromise(promise2, y, resolve, reject) // 直到解析他不是promise位置
},
(r) => {
// reason
if (called) return
called = true
reject(r)
}
)
} else {
// {} {then:{}}
resolve(x) // 常量
}
} catch (e) {
if (called) return
called = true
reject(e)
}
} else {
resolve(x) // 说明返回的是一个普通值 直接将他放到promise2.resolve中
}
}
class Promise {
constructor(executor) {
this.status = PENDING // promise默认的状态
this.value = undefined // 成功的原因
this.reason = undefined // 失败的原因
this.onResolvedCallbacks = [] // 存放成功的回调方法
this.onRejectedCallbacks = [] // 存放失败的回调方法
const resolve = (value) => {
// 成功resolve函数
if (value instanceof Promise) {
return value.then(resolve, reject)
}
if (this.status === PENDING) {
this.value = value
this.status = FULFILLED // 修改状态
// 发布
this.onResolvedCallbacks.forEach((fn) => fn())
}
}
const reject = (reason) => {
// 失败的reject函数
if (this.status === PENDING) {
this.reason = reason
this.status = REJECTED // 修改状态
this.onRejectedCallbacks.forEach((fn) => fn())
}
}
try {
executor(resolve, reject)
} catch (e) {
reject(e)
}
}
// then中的参数是可选的
then(onFulfilled, onRejected) {
// onFulfilled, onRejected
onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (v) => v
onRejected = typeof onRejected === "function" ? onRejected : (err) => { throw err }
// 用于实现链式调用
let promise2 = new Promise((resolve, reject) => {
// 订阅模式
if (this.status == FULFILLED) {
// 成功调用成功方法
setTimeout(() => {
try {
let x = onFulfilled(this.value)
// 此x 可能是一个promise, 如果是promise需要看一下这个promise是成功还是失败 .then ,如果成功则把成功的结果 调用promise2的resolve传递进去,如果失败则同理
// 总结 x的值 决定是调用promise2的 resolve还是reject,如果是promise则取他的状态,如果是普通值则直接调用resolve
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
}
if (this.status === REJECTED) {
// 失败调用失败方法
setTimeout(() => {
try {
let x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
}
if (this.status == PENDING) {
// 代码是异步调用resolve或者reject的
this.onResolvedCallbacks.push(() => {
// 切片编程 AOP
setTimeout(() => {
try {
// todo...
let x = onFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
})
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
// todo...
let x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
}, 0)
})
}
})
return promise2
}
}
module.exports = Promise
至此,一个手写版本的Promise就实现了。