首页 前端知识 基于 Promise A 规范使用Typescript实现Promise

基于 Promise A 规范使用Typescript实现Promise

2024-05-23 20:05:27 前端知识 前端哥 716 142 我要收藏

一. 实现同步的promise

首先先来写一个🌰,一个同步执行的Promise,执行完Promise函数中的内容后,执行其.then中的逻辑:

let p = new Promise((resolve, reject) => {
  resolve('小黄瓜')
}).then((val)=>{
  console.log(val, 'val'); // 小黄瓜
}, () => {})

根据上面例子得执行结果,我们可以得出一个很简单的结论:Promise是一个构造函数(类),接受一个匿名函数,该函数接受两个参数,分别是resolve函数和reject函数,分别代表变更Promise状态为"成功"和"失败"。
当函数执行完毕后,会调用Promise的实例方法then,将执行结果传递给then方法,然后执行then方法中的逻辑。

那么先实现一个Promise类:

export default class TinyPromise<T = any> {
  // 当前promise状态
  private _status = 'pedding'
  // 当前执行状态接收的值,用于传递给then函数
  private _value: any

  constructor(executor: ExecutorType<T>) {
    // 使用try进行错误捕获,如果执行失败直接调用_reject返回错误
    try {
      // 调用用户传递的函数,将_resolve和_reject作为参数传递
      executor(this._resolve.bind(this), this._reject.bind(this))
    } catch (error) {
      this._reject(error)
    }
  }

  private _resolve: Resolve<T> = (resolveValue) {
    // 变更状态为成功
    this._status = 'fulfilled'
    // 保存执行结果
    this._value = resolveValue
  }
  private _reject: Reject = (error) {
    // 变更状态为失败
    this._status = 'rejected'
    this._value = error
  }
}

在这个Promise类中首先定义了一个泛型T,在用户传递的executor函数将其约束为ExecutorType类型,它的实现是这样的:

type ExecutorType<T> = (resolve: Resolve<T>, reject: Reject) => void

因为我们是直接将_resolve_reject这两个实例方法传入到executor函数中,所以ResolveReject类型当然也可以对_resolve_reject这两个实例方法进行约束:

// _resolve
type Resolve<T = any> = (resolveValue: T | PromiseLike<T>) => void
// _reject
type Reject = (rejectValue: any) => void

为什么_resolve的参数被约束为了T | PromiseLike<T>? PromiseLike是什么?PromiseLike其实是ts内置的一个类型,它的实现是这样的:

interface PromiseLike<T> {
    /**
     * Attaches callbacks for the resolution and/or rejection of the Promise.
     * @param onfulfilled The callback to execute when the Promise is resolved.
     * @param onrejected The callback to execute when the Promise is rejected.
     * @returns A Promise for the completion of which ever callback is executed.
     */
    then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): PromiseLike<TResult1 | TResult2>;
}

其实就是定义了一个包含有then方法,接受onfulfilledonrejected函数的接口,可以理解为是一个"Promise"类型,相当于使用它来代表Promise进行约束。
我们的_resolve方法未来可以接受一个Promise作为参数,所以将参数的约束定义为T | PromiseLike<T>

然后将_resolve_reject函数的参数保存到_value中,在未来调用then方法的时候使用。

这里还有一个优化的点,我们可以将fulfilledrejectedpedding三种状态定义为枚举值,便于维护:

enum PROMISESTATUS {
  PENDING = 'pending',
  FULFILLED = 'fulfilled',
  REJECTED = 'rejected'
}

相应的,我们的代码也可以改造成这样:

export default class TinyPromise<T = any> {
  // 初始状态为pedding
  private _status = PROMISESTATUS.PENDING
  private _value: any

  constructor(executor: ExecutorType<T>) {
    try {
      executor(this._resolve.bind(this), this._reject.bind(this))
    } catch (error) {
      this._reject(error)
    }
  }

  private _resolve: Resolve<T> = (resolveValue) {
    this._status = PROMISESTATUS.FULFILLED
    this._value = resolveValue
  }
  private _reject: Reject = (error) {
    this._status = PROMISESTATUS.REJECTED
    this._value = error
  }
}

接下来就可以实现then方法了,其实then方法的作用很简单,包括以后实现的更复杂的实现。它的核心作用只有一个,就是作为executor函数执行后的回调函数
then方法接收两个函数作为参数,其中第一个函数代表执行成功状态的回调,第二个函数代表执行失败状态的回调。

// 暂时将resolveInThen与rejectinThen定义为any
public then(resolveInThen: any, rejectinThen: any) {
  const {
    _value,
    _status
  } = this

  if(_status === PROMISESTATUS.FULFILLED) {
    resolveInThen(_value)
  }

  if(_status === PROMISESTATUS.REJECTED) {
    rejectinThen(_value)
  }
}

这就实现了一个最基本的同步执行的Promise来试一下上面的🌰:

let p = new TinyPromise((resolve, reject) => {
  resolve('小黄瓜')
}).then((val)=>{
  console.log(val, 'val'); // 小黄瓜
}, () => {})

二. 实现异步的Promise & 链式调用

其实上面实现的同步功能没有什么作用,因为Promise本来就是处理异步,如果只能执行同步代码,那还有什么用😂。

let p = new TinyPromise((resolve) => {
  setTimeout(() => {
    resolve('小黄瓜')
  }, 1000);
}).then((val)=>{
  console.log(val, '第一个then');
  return '小南瓜'
}, () => {}).then((val)=>{
  console.log(val, '第二个then');
}, () => {})

上面这个🌰我们想要实现的效果是1秒钟后执行resolve函数,传入’小黄瓜’,然后执行第一个then函数,将执行器的参数进行传递,最后执行第二个then函数,把第一个then函数的返回值当作参数传入。

如果按照上一节实现的功能,肯定是满足不了目前的需求,首先按照js的执行机制,我们在执行器内定义了一个定时器,当js代码遇到异步任务时,会暂时挂起,先执行同步代码,所以在这个例子中,会先执行then函数的代码,而此时Promise的状态依然是pedding,因为执行器定义了一个定时器,所以在1秒钟后Promise的状态才被变更为fulfilledthen函数中的代码无法被正确执行。

其实解决的思路也很简单,我们如果能让then函数内接收的函数执行晚于执行器内的异步代码就可以了!如果执行then方法时,Promise的状态还是处于pedding状态,那么此时在执行器内的逻辑就肯定是异步代码!然后就将then方法传入的函数先收集,再执行!先将回调函数收集起来,等异步操作执行完毕后,再执行回调函数。

那么链式调用怎么实现呢,其实直接返回一个Promise就可以了,返回一个Promise实例,当然可以在后面继续调用then方法。

TinyPromise进行改造:

  export default class TinyPromise<T = any> {
  private _status = PROMISESTATUS.PENDING
  private _value: any
  // 成功回调函数队列
  private _fulfilledQueues: Function[] = []
  // 失败回调函数队列
  private _rejectedQueues: Function[] = []

  constructor(executor: ExecutorType<T>) {
    try {
      executor(this._resolve.bind(this), this._reject.bind(this))
    } catch (error) {
      this._reject(error)
    }
  }

  _resolve(resolveValue: any) {
    if(this._status !== PROMISESTATUS.PENDING) return
    const runFulfilled = (value: any) => {
      let cb;
      // 取出每个函数执行
      while(cb = this._fulfilledQueues.shift()) {
        cb(value)
      }
    }
    // 更改状态 保存参数 执行回调函数
    this._value = resolveValue
    this._status = PROMISESTATUS.FULFILLED
    runFulfilled(resolveValue)
  }
  _reject(error: any) {
    this._status = PROMISESTATUS.REJECTED
    this._value = error
    let cb;
    while (cb = this._rejectedQueues.shift()) {
      cb(error)
    }
  }

  then(resolveInThen: any, rejectinThen: any) {
    const {
      _value,
      _status
    } = this
    // 返回Promise实例
    return new TinyPromise((resolveNext, rejectNext) => {
      // 封装执行成功的操作
      const fulfilled = (val: any) => {
        let res = resolveInThen(val);
        // 向后传递执行结果
        resolveNext(res)
      }
      // 封装执行失败的操作
      const rejected = (error: any) => {
        let res = rejectinThen(error);
        // 向后传递执行结果
        rejectNext(res)
      }
      // 判断当前Promise状态:pedding:保存 fulfilled/rejected:直接执行
      switch(_status) {
        case PROMISESTATUS.PENDING:
          this._fulfilledQueues.push(fulfilled)
          this._rejectedQueues.push(rejected)
        break
        case PROMISESTATUS.FULFILLED:
          fulfilled(_value)
        break
        case PROMISESTATUS.REJECTED:
          rejected(_value)
        break
      }
    })
  }
}

我们定义了两个队列:_fulfilledQueues_rejectedQueues,分别用于保存成功和失败的回调函数,在异步操作执行完毕后,取出两个队列中的函数进行执行。

执行上文中的🌰:

小黄瓜 第一个异步函数
小南瓜 第二个异步函数

三. 实现多级多异步

接下来实现在then方法中再次定义异步方法,此时应该将后续的then作为异步then函数的回调函数来调用。

let p = new TinyPromise((res) => {
  setTimeout(() => {
    res('第一个异步Promise')
  }, 1000)
}).then((val: any)=>{
  console.log(val, '第一个then');
  // 再次定义异步任务
  return new TinyPromise((res, rej) => {
    setTimeout(() => {
      res('第二个异步Promise')
    }, 1000)
  })
}, () => {}).then((val: any)=>{
  // 此时的then应当作为上一个异步的then
  console.log(val, '第二个then');
}, () => {})

在增加了多异步的逻辑之后要在then方法中定义执行函数时,对各种情况进行判断:

  • 如果参数为非函数,则直接向后传递
  • 如果参数为函数,执行函数,并保存执行结果向后传递
  • 如果参数为Promise,调用then

接下来就实现改造后的then方法:

then(resolveInThen: any, rejectinThen: any) {
    const {
      _value,
      _status
    } = this
    return new TinyPromise((resolveNext, rejectNext) => {

      const fulfilled = (val: any) => {
        try {
          // 非函数,直接向后传递
          if (!isFunction(resolveInThen)) {
            resolveNext(val)
          } else {
            // 执行函数
            let res = resolveInThen(val);
            // Promise调用then
            if (isPromise(res)) {
              res.then(resolveNext, rejectNext)
            } else {
              // 返回结果
              resolveNext(res)
            }
          }
        } catch (error) {
          rejectNext(error)
        }
      }
      // 失败状态的处理逻辑类似
      const rejected = (error: any) => {
        try {
          if (!isFunction(rejectinThen)) {
            rejectNext(error)
          } else {
            let res = rejectinThen(error);
            if (isPromise(res)) {
              res.then(resolveNext, rejectNext)
            } else {
              resolveNext(res)
            }
          }
        } catch (error) {
          rejectNext(error)
        }
      }

      switch(_status) {
        case PROMISESTATUS.PENDING:
          this._fulfilledQueues.push(fulfilled)
          this._rejectedQueues.push(rejected)
        break
        case PROMISESTATUS.FULFILLED:
          fulfilled(_value)
        break
        case PROMISESTATUS.REJECTED:
          rejected(_value)
        break
      }
    })
  }

判断是否为函数,使用ts中的自定义类型收窄:

export const isFunction = (val: unknown): val is Function =>
  typeof val === 'function'

ts中使用is语法进行类型收窄,如果返回结果为true,那么此分支会被推断为Function,判断Promise类型类似:

export const isPromise = <T = any>(val: any): val is TinyPromise<T> => {
  return val instanceof TinyPromise
}

至此,then方法的功能已经基本上实现了,而对于ts的类型定义还是非常简陋,可以借鉴PromiseLink来对我们的then方法进行更精确的约束:

// then方法的返回值为Promise
public then<TRes1 = T, TRes2 = never>(resolveInThen?: onFulfilled<T, TRes1>, rejectinThen?: onRejected<TRes2>):
    TinyPromise<TRes1 | TRes2> {}

// then方法的参数可以为空
// 如果不为空,则返回值可以为Promise
type onFulfilled<T, TRes1> = ((value: T) => TRes1 | PromiseLike<TRes1>) | undefined | null
type onRejected<TRes2> = ((err: any) => TRes2 | PromiseLike<TRes2>) | undefined | null

然后就可以执行一下本节开头的例子:

第一个异步Promise 第一个then
第二个异步Promise 第二个then

由于我们已经实现了then方法之间的参数传递,所以也实现了穿透的功能:

new TinyPromise((reslove) => {
  reslove('hello')
})
  .then()
  .then()
  .then()
  .then((res) => {
    console.log(res) // 'hello'
  })

四. 执行器传入Promise

问题又又又来了,如果想在执行器中传入Promise呢?就想这样:

let p = new TinyPromise((res) => {
  setTimeout(() => {
    let t = new TinyPromise((res2, rej2) => {
      res2('异步1')
    })
    // 在resolve中传入一个promise
    res(t)
  }, 1000)
}).then((val:any) => {
  console.log(val, '1');

  return new TinyPromise((res, rej) => {
    setTimeout(() => {
      res('异步2')
    }, 1000)
  })
}, () => { }).then((val: any) => {
  console.log(val, '2');
}, () => { })

此时我们希望在第一个定时器执行完毕后调用res传入Promise,然后将后续的then方法都挂载到这个Promise上。

此时就需要修改_resolve方法了:

private _resolve: Resolve<T> = (resolveValue) => {
    const run = () => {
      if (this._status !== PROMISESTATUS.PENDING) return

      const runFulfilled = (value: any) => {
        let cb;
        while (cb = this._fulfilledQueues.shift()) {
          cb(value)
        }
      }

      const runRejected = (error: any) => {
        let cb
        while (cb = this._rejectedQueues.shift()) {
          cb(error)
        }
      }
      // 判断参数是否为Promise?
      if (isPromise(resolveValue)) {
        // 定义then方法,手动变更状态
        resolveValue.then((val) => {
          this._value = val
          this._status = PROMISESTATUS.FULFILLED
          runFulfilled(val)
        }, (err) => {
          this._value = err
          this._status = PROMISESTATUS.REJECTED
          runRejected(err)
        })
      } else {
        // 不为Promise,直接变更状态,执行回调函数
        this._value = resolveValue
        this._status = PROMISESTATUS.FULFILLED
        runFulfilled(resolveValue)
      }
    }
    // 直接将resolve函数变为异步
    setTimeout(run, 0);
  }

_resolve函数中判断函数是否为Promise?如果是的话,就手动指定then函数,变更状态。

_reject函数类似,只不过不需要处理这种情况了:

private _reject: Reject = (error) => {
    if (this._status !== PROMISESTATUS.PENDING) return

    const run = () => {
      this._status = PROMISESTATUS.REJECTED
      this._value = error
      let cb;
      while (cb = this._rejectedQueues.shift()) {
        cb(error)
      }
    }
    setTimeout(run, 0)
  }

此时执行本节的例子:

异步1 1
异步2 2

五. 其他函数

catch

catch函数只要用于捕获错误,而本身又可以继续调用then函数,所以直接使用then函数来传递一个错误即可:

 catch<TRes>(onRejected: onRejected<TRes>): TinyPromise<T | TRes> {
    return this.then(null, onRejected)
  }

resolve

resolve函数是一个静态方法,通过Promise.resolve来调用,直接返回一个成功状态的Promise,如果参数不是Promise,也不自动进行转换:

static resolve<T>(value: T | PromiseLike<T>): TinyPromise<T> {
  // 判断是否为Promise
  if (isPromise(value)) {
    return value;
  }

  return new TinyPromise(resolve => {
    resolve(value);
  });
}

reject

reject函数返回一个失败状态的Promise

static reject<T = never>(error: any): TinyPromise<T> {
  return new TinyPromise((resolve, reject) => reject(error))
}

all

Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。

const p = Promise.all([p1, p2, p3]);

上面代码中,Promise.all()方法接受一个数组作为参数,p1、p2、p3都是 Promise 实例,如果不是,就会先调用下面讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。另外,Promise.all()方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。

p的状态由p1、p2、p3决定,分成两种情况。

(1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。

(2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。

static all<T>(list: T[]): TinyPromise<T[]> {
  let values: any[] = []
  // 计数
  let count = 0
  // 返回一个Promise,便于后续使用then方法
  return new TinyPromise((resolve, reject) => {
    for (let [k, v] of list.entries()) {
      // 此时的this为Promise自身
      this.resolve(v).then((res: any) => {
        // 使用下标,保证顺序
        values[k] = res
        count++
        // 遍历完成,全部then执行完毕后,调用then方法返回
        if (count === list.length) return resolve(values)
      }, (err: any) => {
        reject(err)
      })
    }
  })
}

遍历数组,因为我们直接调用了Promise.resolve方法,在内部会直接将非Promise也会直接进行转换,为每个遍历值创建一个成功状态的Promise,并绑定then方法,当所有的值都被遍历完成后,then方法执行完毕后,count这个计数的值也会与数组的长度相等,此时返回结果数组。

完整代码

type Resolve<T = any> = (resolveValue: T | PromiseLike<T>) => void
type Reject = (rejectValue: any) => void

type ExecutorType<T> = (resolve: Resolve<T>, reject: Reject) => void

// 非函数 / 返回值/promise
type onFulfilled<T, TRes1> = ((value: T) => TRes1 | PromiseLike<TRes1>) | undefined | null
type onRejected<TRes2> = ((err: any) => TRes2 | PromiseLike<TRes2>) | undefined | null

enum PROMISESTATUS {
  PENDING = 'pending',
  FULFILLED = 'fulfilled',
  REJECTED = 'rejected'
}

export const isFunction = (val: unknown): val is Function =>
  typeof val === 'function'

export const isPromise = <T = any>(val: any): val is TinyPromise<T> => {
  return val instanceof TinyPromise
}

export default class TinyPromise<T = any> {
  public _status: PROMISESTATUS = PROMISESTATUS.PENDING
  public _value!: T | PromiseLike<T>
  private _fulfilledQueues: Resolve[] = []
  private _rejectedQueues: Reject[] = []

  constructor(executor: ExecutorType<T>) {
    try {
      executor(this._resolve.bind(this), this._reject.bind(this))
    } catch (error) {
      this._reject(error)
    }
  }

  private _resolve: Resolve<T> = (resolveValue) => {
    const run = () => {
      if (this._status !== PROMISESTATUS.PENDING) return

      const runFulfilled = (value: any) => {
        let cb;
        while (cb = this._fulfilledQueues.shift()) {
          cb(value)
        }
      }

      const runRejected = (error: any) => {
        let cb
        while (cb = this._rejectedQueues.shift()) {
          cb(error)
        }
      }

      if (isPromise(resolveValue)) {
        resolveValue.then((val) => {
          this._value = val
          this._status = PROMISESTATUS.FULFILLED
          runFulfilled(val)
        }, (err) => {
          this._value = err
          this._status = PROMISESTATUS.REJECTED
          runRejected(err)
        })
      } else {
        this._value = resolveValue
        this._status = PROMISESTATUS.FULFILLED
        runFulfilled(resolveValue)
      }
    }
    setTimeout(run, 0);
  }

  private _reject: Reject = (error) => {
    if (this._status !== PROMISESTATUS.PENDING) return

    const run = () => {
      this._status = PROMISESTATUS.REJECTED
      this._value = error
      let cb;
      while (cb = this._rejectedQueues.shift()) {
        cb(error)
      }
    }
    setTimeout(run, 0)
  }

  public then<TRes1 = T, TRes2 = never>(resolveInThen?: onFulfilled<T, TRes1>, rejectinThen?: onRejected<TRes2>):
    TinyPromise<TRes1 | TRes2> {
    const {
      _value,
      _status
    } = this
    return new TinyPromise((resolveNext, rejectNext) => {

      const fulfilled = (val: any) => {
        try {
          if (!isFunction(resolveInThen)) {
            resolveNext(val)
          } else {
            let res = resolveInThen(val);
            if (isPromise(res)) {
              res.then(resolveNext, rejectNext)
            } else {
              resolveNext(res)
            }
          }
        } catch (error) {
          rejectNext(error)
        }
      }

      const rejected = (error: any) => {
        try {
          if (!isFunction(rejectinThen)) {
            rejectNext(error)
          } else {
            let res = rejectinThen(error);
            if (isPromise(res)) {
              res.then(resolveNext, rejectNext)
            } else {
              resolveNext(res)
            }
          }
        } catch (error) {
          rejectNext(error)
        }
      }

      switch (_status) {
        case PROMISESTATUS.PENDING:
          this._fulfilledQueues.push(fulfilled)
          this._rejectedQueues.push(rejected)
          break
        case PROMISESTATUS.FULFILLED:
          fulfilled(_value)
          break
        case PROMISESTATUS.REJECTED:
          rejected(_value)
          break
      }
    })
  }

  catch<TRes>(onRejected: onRejected<TRes>): TinyPromise<T | TRes> {
    return this.then(null, onRejected)
  }

  static resolve<T>(value: T | PromiseLike<T>): TinyPromise<T> {
    if (isPromise(value)) {
      return value;
    }

    return new TinyPromise(resolve => {
      resolve(value);
    });
  }

  static reject<T = never>(error: any): TinyPromise<T> {
    return new TinyPromise((resolve, reject) => reject(error))
  }

  static all<T>(list: T[]): TinyPromise<T[]> {
    let values: any[] = []
    let count = 0
    return new TinyPromise((resolve, reject) => {
      for (let [k, v] of list.entries()) {
        this.resolve(v).then((res: any) => {
          values[k] = res
          count++
          if (count === list.length) return resolve(values)
        }, (err: any) => {
          reject(err)
        })
      }
    })
  }
}
转载请注明出处或者链接地址:https://www.qianduange.cn//article/9188.html
标签
评论
发布的文章

jQuery是什么?如何使用?

2024-03-12 01:03:24

js延迟加载的六种方式

2024-05-30 10:05:51

HTML5懒人代码模板

2024-05-30 10:05:57

大家推荐的文章
会员中心 联系我 留言建议 回顶部
复制成功!