前言:promise用了不少,但是一直没有深究它是怎么实现的,今天把自己的理解梳理一下
promise也是一个类,我们先class一个类,再导出一下。promise有一个状态,默认是pending,并且是可以改变的,要先定义下
// promise.js
class Promise {
// 构造函数
constructor(){
// promise的状态
this.status = 'pending'
}
}
module.exprots = Promise
使用的话,new一个就好了,不过这里要先导入我们自己写的promise
// base.js
const Promise = require('./promise')
// new Promise并传入一个函数作为参数,这个函数有两个返回值,一个是成功函数,一个是失败失败函数
let promise = new Promise((resolve,reject)=>{
})
回到promise.js,在构造函数的参数中加上传入的这个函数,并且在里面调用且传出成功态和失败态函数
// promise.js
class Promise {
// 构造函数
constructor(fn){
// promise的状态
this.status = 'pending'
// 定义成功态函数
let resolve = (succ)=>{
}
// 定义失败态函数
let reject = (err)=>{
}
fn(resolve,reject)
}
}
module.exprots = Promise
我们再在构造函数里新增两个变量分别存储调用resolve传进来的值和调用reject传进来的值。promise默认的状态是pending,我们在调用resolve和reject时都会改变它的状态,并且只有在promise状态为pending时才可以修改它的状态,还有特殊情况就是当报错时,会直接调用失败方法reject。
// promise.js
class Promise {
// 构造函数
constructor(fn){
this.status = 'pending' // promise的状态
this.resolveData = undefined // 存储调用resolve传进来的值
this.rejectData = undefined //存储调用reject传进来的值
let resolve = (succData)=>{
if(this.status === 'pending'){
this.status = 'resolve'
this.resolveData = succData
}
}
let reject = (errData)=>{
if(this.status === 'pending'){
this.status = 'reject'
this.rejectData = errData
}
}
//考虑到报错直接reject,这里用try catch处理下
try{
fn(resolve,reject)
}catch (e){
reject(e, '报错啦')
}
}
}
module.exprots = Promise
基本上按上面的,new Promise就可以使用最基础的功能了
// base.js
const Promise = require('./promise')
// new Promise并传入一个函数作为参数,这个函数有两个返回值,一个是成功函数,一个是失败失败函数
let promise = new Promise((resolve,reject)=>{
//先写我们的业务代码
//再调用成功函数或失败函数跳出这个函数
resolve('成功啦')
//reject('失败啦')
})
我们再promise加上then()方法,因为所有的用户都可以使用promise的then()方法,所以不能写在构造函数里面
// promise.js
class Promise {
// 构造函数
constructor(fn){
this.status = 'pending' // promise的状态
this.resolveData = undefined // 存储调用resolve传进来的值
this.rejectData = undefined //存储调用reject传进来的值
let resolve = (succData)=>{
if(this.status === 'pending'){
this.status = 'resolve'
this.resolveData = succData
}
}
let reject = (errData)=>{
if(this.status === 'pending'){
this.status = 'reject'
this.rejectData = errData
}
}
//考虑到报错直接reject,这里用try catch处理下
try{
fn(resolve,reject)
}catch (e){
reject(e, '报错啦')
}
}
//then方法同样接受两个函数,成功函数和失败函数
then(succFn,errFn){
//这里的执行逻辑是根据new Promise结束的状态判断的
if(this.status === 'resolve'){
//调用方法的同时,要把new Promise的之前存的值传给它
succFn(this.resolveData)
}
if(this.status === 'resolve'){
//调用方法的同时,要把new Promise的之前存的值传给它
errFn(this.rejectData)
}
}
}
module.exprots = Promise
按上面的,new Promise的then()方法最基本的一个使用也可以了,下面我们考虑特殊情况,如果在new Promise里面加了定时器之类的异步逻辑,就有可能造成定时器里面的resolve()和reject()方法不会执行,因为我们写的promise都是同步任务,会先执行,等定时器时间到了再执行resolve()或reject()时,状态就已经丢失了
// base.js
const Promise = require('./promise')
// new Promise并传入一个函数作为参数,这个函数有两个返回值,一个是成功函数,一个是失败失败函数
let promise = new Promise((resolve,reject)=>{
console.log('这里立即执行')
setTimeout(()=>{
resolve('3秒后再执行成功回调')
},3000)
})
promise.then((successData) => {
console.log(successData, 'then成功回调');
}, (errData) => {
console.log(errData, 'then失败回调');
})
上面这样加了定时器的话,console.log(successData, 'then成功回调')就永远都不会打印了,为了解决上面的问题,我们需要给promise把成功回调和失败回调都定义一个数组,在then()方法调用时只要状态为pending都存起来。这样在后面异步任务定时器执行完之后调用resolve或reject方法的时候,再循环这个数组并调用里面的方法。
// promise.js
class Promise {
// 构造函数
constructor(fn){
this.status = 'pending' // promise的状态
this.resolveData = undefined // 存储调用resolve传进来的值
this.rejectData = undefined //存储调用reject传进来的值
this.onSuccessCallback = [] // 定义一个数组存放成功回调的函数
this.onErrCallback = [] // 定义一个数组存放失败回调的函数
let resolve = (succData)=>{
if(this.status === 'pending'){
this.status = 'resolve'
this.resolveData = succData
this.onSuccessCallback.forEach((item) => {
item()
})
}
}
let reject = (errData)=>{
if(this.status === 'pending'){
this.status = 'reject'
this.rejectData = errData
this.onErrCallback.forEach((item) => {
item()
})
}
}
//考虑到报错直接reject,这里用try catch处理下
try{
fn(resolve,reject)
}catch (e){
reject(e, '报错啦')
}
}
//then方法同样接受两个函数,成功函数和失败函数
then(succFn,errFn){
//这里的执行逻辑是根据new Promise结束的状态判断的
if(this.status === 'resolve'){
//调用方法的同时,要把new Promise的之前存的值传给它
succFn(this.resolveData)
}
if(this.status === 'resolve'){
//调用方法的同时,要把new Promise的之前存的值传给它
errFn(this.rejectData)
}
//这里一定要是pending状态才存起来
if (this.status === 'pending') {
//传进去的参数要用一个函数包住,不然传进去的就是值而不是方法
this.onSuccessCallback.push(() => { ()=>{success(this.resolveValue) }})
this.onErrCallback.push(() => { ()=>{err(this.rejectValue) }})
}
}
}
module.exprots = Promise
按上面的代码,就能解决定时器这种异步问题啦,最基础的promise功能也基本上达到了。
下面再看一下promise的链式调用
// promise.js
//定义一个方法判断x是否是一个promise
const resolvePromise = (promise2, x, resolve, reject) => {
//判断 可以你的promise和别的promise要混用
//可能不同的promise库之间要相互调用
if (promise2 === x) {
//x如果和promise2是同一个的话x 永远不能成功或者失败,所以就卡死了,我们需要直接报错即可
return reject(new TypeError('Chaining cycle detected for promise #<Promise>'));
}
//判断x的状态,到底是不是promise
if ((typeof x === 'object' && x !== null) || x === 'function') {
//x 必须是一个对象或者是函数
let called = false // 增加一个变量,保证不能多次调成功或调失败
try {
let then = x.then // 取出zhen方法,这个then方法是采用defineProperty来定义的
if (typeof then === 'function') {
// 判断then是不是一个函数,如果then不是一个函数,说明不是promise
// 只能认准它是一个promise了
then.call(x, (y) => {// 如果x是一个promise 就采用promise的返回结果
if (called) return //让一次成功之后就不能再走了
called = true
// 这里防止y又是promise,递归一下
resolvePromise(y)
}, (r) => {
if (called) return
called = true
reject(r)
})
} else {
// 如果then不是函数,是普通值,直接触发promise2的成功逻辑
resolve(x)
}
} catch (e) {
if (called) return
called = true
reject(e) // 取then失败了,直接触发promise2的失败逻辑
}
} else {
// 否则直接成功即可
resolve(x)
}
}
class Promise {
// 构造函数
constructor(fn) {
this.status = 'pending' // promise的状态
this.resolveData = undefined // 存储调用resolve传进来的值
this.rejectData = undefined //存储调用reject传进来的值
this.onSuccessCallback = [] // 定义一个数组存放成功回调的函数
this.onErrCallback = [] // 定义一个数组存放失败回调的函数
let resolve = (succData) => {
if (this.status === 'pending') {
this.status = 'resolve'
this.resolveData = succData
this.onSuccessCallback.forEach((item) => {
item()
})
}
}
let reject = (errData) => {
if (this.status === 'pending') {
this.status = 'reject'
this.rejectData = errData
this.onErrCallback.forEach((item) => {
item()
})
}
}
//try catch 只能捕获同步异常,定时器内的就捕获不到了,所以定时器内的异常要单独处理
try {
fn(resolve, reject)
} catch (e) {
reject(e, '报错啦')
}
}
//then方法同样接受两个函数,成功函数和失败函数
then (succFn, errFn) {
// 增加可选参数处理
succFn = typeof succFn === 'function' ? succFn : val => val
errFn = typeof errFn === 'function' ? errFn : err => {
throw err
}
//为了能实现链式调用这里我们以递归的方式定义一个promise
let promise2 = new Promise((resolve, reject) => {
if (this.status === 'resolve') {
//定义一个异步任务定时器,防止把promise2传出的时候,promise会提示未定义
setTimeout(() => {
//异步任务的异常在构造函数的try catch中捕获不到,这里要单独处理
try {
//将每一个值的都存起来给promise使用
let x = succFn(this.resolveData)
//在类的外面定义一个roselvePromise方法,并且把promise2的resolve把值传出去
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e, '报错啦')
}
}, 0)
}
if (this.status === 'resolve') {
setTimeout(() => {
try {
//将每一个值的都存起来给promise使用
let x = errFn(this.rejectData)
//在类的外面定义一个roselvePromise方法,并且把promise2的resolve把值传出去
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e, '报错啦')
}
}, 0)
}
if (this.status === 'pending') {
this.onSuccessCallback.push(() => {
() => {
setTimeout(() => {
try {
//将每一个值的都存起来给promise使用
let x = success(this.resolveValue)
//在类的外面定义一个roselvePromise方法,把promise2的resolve把值传出去
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e, '报错啦')
}
}, 0)
}
})
this.onErrCallback.push(() => {
() => {
setTimeout(() => {
try {
//将每一个值的都存起来给promise使用
let x = err(this.rejectValue)
//在类的外面定义一个roselvePromise方法,把promise2的resolve把值传出去
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e, '报错啦')
}
}, 0)
}
})
}
})
// 在执行完成后将promise2返回
return promise2
}
}
module.exprots = Promise
根据上面的代码,基本上一个完整的promise方法就实现了