2021年2月14日星期日

JS Promise的用法, 以及自己模拟一个Promise

注: 本文中写的类只是为了了解Promise类的内部原理而模拟出来一个, 并不一定符合类似的规范或者效率多么高, 但是基本的功能还是实现了的.
注: 本文代码运行环境: NodeJS v14.9.0

用法

如下, 这是一个传统的使用回调函数的异步代码

function getAnInt(callback) { setTimeout(() => {  callback(81) }, 500)}function sqrt(n, resolve, reject) { setTimeout(() => {  let res = Math.sqrt(n)  if (parseInt(res) === res) {   resolve(res)  } else {   reject("cannot get an int")  } }, 500)}let errHandler = err => console.log("Error " + err)getAnInt(v1 => { console.log(v1) sqrt(v1, v2 => {  console.log(v2)  sqrt(v2, v3 => {   console.log(v3)   sqrt(v3, v4 => {    console.log(v4)   }, errHandler)  }, errHandler) }, errHandler)})

执行结果:

8193Error cannot get an int

有没有感觉眼花缭乱? 这金字塔状的代码被亲切地称为回调地狱, 下面就是我们的主角Promise上场的时候了, 酱酱酱酱

function getAnInt() { return new Promise((resolve, reject) => {  setTimeout(() => {   resolve(81)  }, 500) })}function sqrt(n) { return new Promise((resolve, reject) => {  setTimeout(() => {   let res = Math.sqrt(n)   if (parseInt(res) === res) {    resolve(res)   } else {    reject("cannot get an int")   }  }, 500) })}getAnInt().then(v1 => { console.log(v1) return sqrt(v1)}).then(v2 => { console.log(v2) return sqrt(v2)}).then(v3 => { console.log(v3) return sqrt(v3)}).then(v4 => { console.log(v4)}).catch(err => { console.log("Error " + err)})

执行结果:

8193Error cannot get an int

结果一模一样, 但是这个代码写出来的感觉, 就是要清晰了好多好多好多好多好多好多好多好多好多好多好多好多

介绍

Promise/A+标准中定义了Promise到底是个什么东西, 这里挑出重点部分, 其余的规范如果想看的话点这里去官网

  • promise 含有then方法, 没有规定其它的方法.
  • then方法会返回一个新的promise
  • then方法的参数是onFulfilled, onRejected, 它们都是可选的(当然都是函数类型)
  • promise有三个状态, pending(代办), fulfilled(完成)rejected(被拒绝), 状态只能从pending转成另外两个, 然后就不能再转了.
  • 如果onRejected或者onFulfilled返回了一个Promise对象, 需要得出它的结果再传给下一个then方法里对应的地方

因为本文代码中有很多的 resolve, 所以这里的代码使用resolved(被解决)代替fulfilled

为什么没有列出来更多的内容呢, 因为其它的内容大多和兼容性有关, 与这个实现原理关系不是太大, 还有的是到具体实现函数的时候才会用到的规范, 所以我没有列出来

注: catch方法是ES6标准里的, 它的原理是then(null, onRejected)

实现

注: 本文代码不考虑throw, 为了只体现原理, 让代码尽可能更简单.

构造函数

Promise的构造函数通常传入一个执行者函数, 这个函数里面可能是异步逻辑(这么说的意思就是也可能不是), 接受两个参数: resolvereject.

  • 调用resolve(value)就代表方法成功执行了, Promise会把resolve中传入的value传给then方法里的参数

  • 调用reject(reason)就是执行出错了, Promise会把reject中传入的reason传给then方法里的参数

好, 下面开始做点准备工作

const Pending = 'pending'const Resolved = 'resolved'const Rejected = 'rejected'class MyPromise {}

诶, 这段代码我感觉不用解释了吧? 下面的我会在注释或者是代码块下方说明

class MyPromise { constructor(executor) {  // 状态  this.status = Pending  // 正常运行返回的结果  this.value = null  // 发生错误的原因  this.reason = null  // 详见这段代码块下面写的 注1  this.onRejected = () => {}  this.onResolved = () => {}    let resolve = value => {   // 如果不是Pending就忽略   if (this.status !== Pending) {    return   }   this.status = Resolved   this.value = value   this.onResolved(value)  }  let reject = reason => {   // 如果不是Pending就忽略   if (this.status !== Pending) {    return   }   this.status = Rejected   this.reason = reason   this.onRejected(reason)  }  // 见 注2  executor(resolve, reject) }}
  • 注1: 这是两个被reject或者resolve后调用的回调函数, 我看的别人实现的版本大多是一个数组, 然后调用的时候一个接一个调用里面的函数.

    我认为对同一个promise调用多次then方法的时候很少, 而且本文只是一个思路展示, 并不严格遵守A+规范, 所以这里就直接写了个什么也没干的函数

    在这里也分析一下, 在then方法调用的时候, 如果调用then时的状态是Pending, 那么就设置一下当前对象里的onRejectedonResolved, 具体设置什么在后面的代码里会提到; 如果状态不是Pending, 就代表这两个函数早就执行完了, 就需要根据this.valuethis.reason具体的调用then函数中传进来的onRejectedonResolved.

  • 注2: 这里直接同步调用了, 没有异步调用. 因为如果这个操作真的需要异步的话, 在executor函数里面就会有异步方法了(如setTimeout), 不需要Promise类给它办.

Then方法

然后就是then方法啦~

注意: then方法要求每次返回新的Promise对象.

先写个框架

then(onResolved, onRejected) { let funcOrNull = f => typeof f === "function" ? f : null onResolved = funcOrNull(onResolved) onRejected = funcOrNull(onRejected) if (this.status === Rejected) { return new MyPromise((resolve, reject) => { }) } else if (this.status === Resolved) { return new MyPromise((resolve, reject) => { }) } else { return new MyPromise((resolve, reject) => { }) }}

Rejected

如果是状态是rejected, 那么

if (this.status === Rejected) { return new MyPromise((resolve, reject) => {  let value = (onRejected === null ? reject : onRejected)(this.reason)  if (value instanceof MyPromise) {   value.then(resolve, reject)  } else {   resolve(value)  } })}

这些实现的代码包括下面的elseif和else块就是最难理解的了, 我当时是好久好久也没有理解, 接下来我会就像数学里面一样分类讨论:

关于Rejected块的详细说明(尽管也就10行)

理解了Rejected块, 那么Resolved块和他几乎一模一样, 只是函数名字不一样而已, 所以我这里会分析的尽可能详细

  • 如果调用的时候是这样的:

    new MyPromise((resolve, reject) => { reject("I rejected the promise")}).then(null, console.log)

    先分析构造方法, 创建Promise对象的时候, 这里它的状态就变成Rejected, 但是其他的什么事都没干, 让我们来看前面的代码

    this.onRejected = () => {}this.onResolved = () => {}let reject = reason => { if (this.status !== Pending) {  return } this.status = Rejected this.reason = reason this.onRejected(reason)}executor(resolve, reject)

    这个时候this.onRejected还是个空函数, 所以调用它也没什么用
    接下来到then方法了, 让我们来看上面if块里的代码

    return new MyPromise((resolve, reject) => { let value = (onRejected === null ? reject : onRejected)(this.reason) if (value instanceof MyPromise) {  value.then(resolve, reject) } else {  resolve(value) }})

    可以看出它执行了let value = onRejected(reason), 然后调用resolve(value), 之后这个新的Promise状态就是Resolved了.

    至于为什么这里要用resolve, 我是通过NodeJS做了个实验看看NodeJS对这件事是怎么干的, 代码如下

    let p1 = new Promise((resolve, reject) => { reject("I rejected the promise")})let p2 = p1.then(null, reason => { return 'I am from onRejected function'})// 这里是为了不管到底是什么状态都能把p1和p2输出出来p2.then(() => console.log(p1, p2), () => console.log(p1, p2))

    输出(原本的执行结果没有换行, 我为了方便看自己加上的)

    Promise { <rejected> 'I rejected the promise' }Promise { 'I am from onRejected function' }

    这就看出来NodeJS是在处理完错误之后把onRejected的返回值用resolve函数处理了


  • 如果调用的时候是这样的

    new MyPromise((resolve, reject) => { reject("I just rejected the promise")}).then(null, null).then(null, console.log)

    这个时候就要考虑不能把错误信息丢掉了, 为了实现这个"穿透"功能, 我们可以研究一下NodeJS是怎么干的

    let p1 = new Promise((resolve, reject) => { reject("I rejected the promise")}).then(null, null)p1.then(() => console.log(p1), () => console.log(p1))

    输出

    Promise { <rejected> 'I rejected the promise' }

    这就很简单了, NodeJS是把新的Promise对象继续调用reject并且传递错误信息. 所以再看上面if块里的代码

    return new MyPromise((resolve, reject) => { let value = (onRejected === null ? reject : onRejected)(this.reason) if (value instanceof MyPromise) {  value.then(resolve, reject) } else {  resolve(value) }})

    可以看出这里也是在onRejected空的时候直接用reject方法把新的Promise对象的状态设置成了Rejected并且也把this.reason错误信息传了过去.
    展现成代码的话, 就是执行了reject(this.reason)

    你可能会疑惑, 那么reject(this.reason)返回值应该是undefined, 然后又调用了resolve(value)是怎么回事呢?
    这里我们要看前面的代码

    let resolve = value => { // 如果不是Pending就忽略 if (this.status !== Pending) {  return } this.status = Resolved this.value = value this.onResolved(value)}

    在调用完reject之后, 这里的status就变成了Rejected, 这个方法就不会调用了呀


    你可能还会疑惑, 这里的代码

    if (value instanceof MyPromise) { value.then(resolve, reject)} else { resolve(value)}

    虽然说你知道返回值是Promise要得出结果, 但这是onRejected返回的值, 为什么第二行要这么写?

    还是老方法, 我们看看NodeJS这个地方怎么实现的

    let p = new Promise((resolve, reject) => { reject("I rejected the promise")}).then(null, reason => { return new Promise((resolve, reject) => {  resolve("Hello~") })}).then(value => { console.log("Value " + value)}, reason => { console.log("Reason " + reason)})

    运行结果

    Value Hello~

    所以说, 这里需要这么写, 让这个then里返回的Promise对象then方法的onResolved方法直接调用新对象的resolvereject方法来操作这个新对象

Resolved

如果上面的都能理解了, 那么下面这个elseif块就特别好理解了

else if (this.status === Resolved) { return new MyPromise((resolve, reject) => {  let value = (onResolved === null ? resolve : onResolved)(this.value)  if (value instanceof MyPromise) {   value.then(resolve, reject)  } else {   resolve(value)  } }} 

Pending

else块里, 也就是状态是Pending的时候, 需要做的事情几乎和上面的ifelseif块一样

Promise对象状态是Pending的时候, 不能通过this.valuethis.reason获取值, 但是, 我们可以通过设置this.onRejectedthis.onResolved这两个函数, 因为当Promiseexecutor执行完的时候一定会调用这两个函数中的一个, 并且调用它们的时候都会带上valuereason, 所以这里的代码需要这么写

else { return new MyPromise((resolve, reject) => {  this.onResolved = value => {   let v = (onResolved === null ? resolve : onResolved)(value)   if (v instanceof MyPromise) {    v.then(resolve, reject)   } else {    resolve(v)   }  }  this.onRejected = reason => {   let v = (onRejected === null ? reject : onRejected)(reason)   if (v instanceof MyPromise) {    v.then(resolve, reject)   } else {    resolve(v)   }  } })}

最后加上一个catch方法, 其实就是一个语法糖, 既然ES6都加上了, 那我也加上吧

catch(onRejected) { return this.then(null, onRejected)}

最后的测试

嘿咻, 终于弄完了, 接下来就是实验新对象的时候啦!(这么说好像有点怪怪的呢)

还是文章开头那熟悉的味道

function getAnInt() { return new MyPromise((resolve, reject) => {  setTimeout(() => {   resolve(81)  }, 500) })}function sqrt(n) { return new MyPromise((resolve, reject) => {  setTimeout(() => {   let res = Math.sqrt(n)   if (parseInt(res) === res) {    resolve(res)   } else {    reject("cannot get an int")   }  }, 500) })}getAnInt().then(v1 => { console.log(v1) return sqrt(v1)}).then(v2 => { console.log(v2) return sqrt(v2)}).then(v3 => { console.log(v3) return sqrt(v3)}).then(v4 => { console.log(v4)}).catch(err => { console.log("Error " + err)})

结果

8193Error cannot get an int

附: 全代码

const Pending = 'pending'const Resolved = 'resolved'const Rejected = 'rejected'class MyPromise { constructor(executor) {  // 状态  this.status = Pending  // 正常运行返回的结果  this.value = null  // 发生错误的原因  this.reason = null  // 见 注1  this.onRejected = () => {}  this.onResolved = () => {}    let resolve = value => {   // 如果不是Pending就忽略   if (this.status !== Pending) {    return   }   this.status = Resolved   this.value = value   this.onResolved(value)  }  let reject = reason => {   // 如果不是Pending就忽略   if (this.status !== Pending) {    return   }   this.status = Rejected   this.reason = reason   this.onRejected(reason)  }  // 见 注2  executor(resolve, reject) } then(onResolved, onRejected) {  let funcOrNull = f => typeof f === "function" ? f : null  onResolved = funcOrNull(onResolved)  onRejected = funcOrNull(onRejected)  if (this.status === Rejected) {   return new MyPromise((resolve, reject) => {    let value = (onRejected === null ? reject : onRejected)(this.reason)    if (value instanceof MyPromise) {     value.then(resolve, reject)    } else {     resolve(value)    }   })  } else if (this.status === Resolved) {   return new MyPromise((resolve, reject) => {    let value = (onResolved === null ? resolve : onResolved)(this.value)    if (value instanceof MyPromise) {     value.then(resolve, reject)    } else {     resolve(value)    }   })  } else {   return new MyPromise((resolve, reject) => {    this.onResolved = value => {     let v = (onResolved === null ? resolve : onResolved)(value)     if (v instanceof MyPromise) {      v.then(resolve, reject)     } else {      resolve(v)     }    }    this.onRejected = reason => {     let v = (onRejected === null ? reject : onRejected)(reason)     if (v instanceof MyPromise) {      v.then(resolve, reject)     } else {      resolve(v)     }    }   })  } } catch(onRejected) {  return this.then(null, onRejected) }}// 测试模块!function getAnInt() { return new MyPromise((resolve, reject) => {  setTimeout(() => {   resolve(81)  }, 500) })}function sqrt(n) { return new MyPromise((resolve, reject) => {  setTimeout(() => {   let res = Math.sqrt(n)   if (parseInt(res) === res) {    resolve(res)   } else {    reject("cannot get an int")   }  }, 500) })}getAnInt().then(v1 => { console.log(v1) return sqrt(v1)}).then(v2 => { console.log(v2) return sqrt(v2)}).then(v3 => { console.log(v3) return sqrt(v3)}).then(v4 => { console.log(v4)}).catch(err => { console.log("Error " + err)})

参考: https://zhuanlan.zhihu.com/p/21834559
https://zhuanlan.zhihu.com/p/183801144









原文转载:http://www.shaoqun.com/a/553571.html

跨境电商:https://www.ikjzd.com/

黑石集团:https://www.ikjzd.com/w/1339.html

友家速递:https://www.ikjzd.com/w/1341


注:本文中写的类只是为了了解Promise类的内部原理而模拟出来一个,并不一定符合类似的规范或者效率多么高,但是基本的功能还是实现了的.注:本文代码运行环境:NodeJSv14.9.0用法如下,这是一个传统的使用回调函数的异步代码functiongetAnInt(callback){setTimeout(()=>{callback(81)},500)}functionsqrt(n,resol
promoted:promoted
tenso:tenso
亚马逊电商退出中国:亚马逊电商退出中国
怎样更好地理解产品,才做得好跨境电商?:怎样更好地理解产品,才做得好跨境电商?
跨境各类产品认证,这些点你需要了解!:跨境各类产品认证,这些点你需要了解!