你不容错过的JavaScript高级语法(再次手写Promise)

简介: 你不容错过的JavaScript高级语法(再次手写Promise)

以前学习过手写Promise,但是只是在看懂的基础上加以"抄袭"代码。所以,下面一篇文章将总结老师的手写思想,然后记录一下。


promise基础用法如果不熟悉的同学,请访问这里promise用法


手写之前我们需要知道的事情


  • promise初始状态为pending。


  • 当调用resolve时,状态变为fulfilled。


  • 当调用reject是,状态变为rejected。


  • 只要状态从pending改变后,该状态将一直保持不变。


  • 抛出错误后,它将调用reject。状态变为rejected。 根据上面的规则,我们可以给出以下结构


  • 定义三个状态常量。


  • 定义成功传递的值。


  • 定义失败的原因。


const PROMISE_STATUS_PENDING = 'pending'
    const PROMISE_STATUS_FULFILLED = 'fulfilled'
    const PROMISE_STATUS_REJECTED = 'rejected'
    class MyPromise {
      constructor(executor) {
        this.status = PROMISE_STATUS_PENDING
        this.value = undefined
        this.reason = undefined
        const resolve = (value) => {
          // 只有状态为pedding时,他才会赋值value,并且改变状态。
          if (this.status === PROMISE_STATUS_PENDING) {
            this.status = PROMISE_STATUS_FULFILLED
            this.value = value
            console.log("resolve被调用")
          }
        }
        const reject = (reason) => {
          if (this.status === PROMISE_STATUS_PENDING) {
            this.status = PROMISE_STATUS_REJECTED
            this.reason = reason
            console.log("reject被调用")
          }
        }
        // 立即执行执行器,即传入promise的函数。当执行器抛出错误,直接调用reject传递错误。
         try {
           executor(resolve, reject)
         } catch (error) {
           reject(error)
         }
      }
    }


测试


const promise = new MyPromise((resolve, reject) => {
  console.log("状态pending")
  resolve(1111)
  reject(2222)
})


网络异常,图片无法展示
|


由上面的输出可以知道,当调用resolve时,promise状态改变,即使调用了reject了,也不会再去执行。


then方法的设计


  • then方法可以接受两个参数。


  • 我们将该参数保留下来,当调用resolve或者reject时,将其加入微任务中,这样就满足了原生Promise调用then方法,将其加入微任务队列的效果。


版本一


const PROMISE_STATUS_PENDING = 'pending'
    const PROMISE_STATUS_FULFILLED = 'fulfilled'
    const PROMISE_STATUS_REJECTED = 'rejected'
    class MyPromise {
      constructor(executor) {
        this.status = PROMISE_STATUS_PENDING
        this.value = undefined
        this.reason = undefined
        const resolve = (value) => {
          if (this.status === PROMISE_STATUS_PENDING) {
            this.status = PROMISE_STATUS_FULFILLED
            // 将其将入微任务队列中
            queueMicrotask(() => {
              this.value = value
              this.onFulfilled(this.value)
            });
          }
        }
        const reject = (reason) => {
          if (this.status === PROMISE_STATUS_PENDING) {
            this.status = PROMISE_STATUS_REJECTED
            queueMicrotask(() => {
              this.reason = reason
              this.onRejected(this.reason)
            })
          }
        }
        try {
          executor(resolve, reject)
        } catch (error) {
          reject(error)
        }
      }
      then(onFulfilled, onRejected) {
        this.onFulfilled = onFulfilled
        this.onRejected = onRejected
      }
    }


测试一


const promise = new MyPromise((resolve, reject) => {
      console.log("状态pending")
      reject(2222)
      resolve(1111)
    })
    // 调用then方法
    promise.then(res => {
      console.log("res1:", res)
      return 1111
    }, err => {
      console.log("err:", err)
    })
    promise.then(res => {
      console.log("res2:", res)
    }, err => {
      console.log("err2:", err)
    })


网络异常,图片无法展示
|


由上面的结果可知,当多次调用同一个promise时,他不会多次执行then方法,因为他会覆盖上一次的onFulfilled, onRejected方法,所以只会执行一次。


测试二


// 异步调用then方法
    setTimeout(() => {
      promise.then(res => {
        console.log("res", res)
      })
    }, 1000);


网络异常,图片无法展示
|


由上面的结果可知,方promise执行完后,才回去执行setTimeout,所以还没有加入到onFulfilled,所以onFulfilled为undefined。


版本二


针对上面的问题,我们可以做出优化


  • 定义onFulfilledFns,onRejectedFns两个数组,分别收集then方法对应的两个参数。


  • 将then方法传递的参数,放在对应的数组中。


  • 当我们将then方法异步调用时,那时promise的状态已经被确定下来,所以,我们需要将then中方法直接调用。


  • 如果改变状态的语句放在微任务内的话,那么非异步调用then也会直接在then方法中执行,如果改变状态的语句放在微任务外部的话,那么调用reject和resolve都会执行。所以就需要给出一个判断if (this.status !== PROMISE_STATUS_PENDING) return, 这样当resolve的微任务执行的时候状态就会改变,再次执行reject的微任务时,就直接return, 反之也是一样。


const PROMISE_STATUS_PENDING = 'pending'
    const PROMISE_STATUS_FULFILLED = 'fulfilled'
    const PROMISE_STATUS_REJECTED = 'rejected'
    class MyPromise {
      constructor(executor) {
        this.status = PROMISE_STATUS_PENDING
        this.value = undefined
        this.reason = undefined
        this.onFulfilledFns = []
        this.onRejectedFns = []
        const resolve = (value) => {
          if (this.status === PROMISE_STATUS_PENDING) {
            // 添加微任务
            queueMicrotask(() => {
              // 如果状态改变,那么将不会再次改变状态,直接在then方法就调用了传递的参数。
              if (this.status !== PROMISE_STATUS_PENDING) return
              this.status = PROMISE_STATUS_FULFILLED
              this.value = value
              this.onFulfilledFns.forEach(fn => {
                fn(this.value)
              })
            });
          }
        }
        const reject = (reason) => {
          if (this.status === PROMISE_STATUS_PENDING) {
            // 添加微任务
            queueMicrotask(() => {
              if (this.status !== PROMISE_STATUS_PENDING) return
              this.status = PROMISE_STATUS_REJECTED
              this.reason = reason
              this.onRejectedFns.forEach(fn => {
                fn(this.reason)
              })
            })
          }
        }
        executor(resolve, reject)
      }
      then(onFulfilled, onRejected) {
        // 如果在then调用的时候, 状态已经确定下来,为了满足异步调用then方法
        if (this.status === PROMISE_STATUS_FULFILLED && onFulfilled) {
          onFulfilled(this.value)
        }
        if (this.status === PROMISE_STATUS_REJECTED && onRejected) {
          onRejected(this.reason)
        }
        // 将成功回调和失败的回调放到数组中
        if (this.status === PROMISE_STATUS_PENDING) {
          this.onFulfilledFns.push(onFulfilled)
          this.onRejectedFns.push(onRejected)
        }
      }
    }


测试三


const promise = new MyPromise((resolve, reject) => {
      console.log("状态pending")
      resolve(1111) // resolved/fulfilled
      reject(2222)
    })
    // 调用then方法多次调用
    promise.then(res => {
      console.log("res1:", res)
    }, err => {
      console.log("err:", err)
    })
    promise.then(res => {
      console.log("res2:", res)
    }, err => {
      console.log("err2:", err)
    })
    // 在确定Promise状态之后, 再次调用then
    setTimeout(() => {
      promise.then(res => {
        console.log("res3:", res)
      }, err => {
        console.log("err3:", err)
      })
    }, 1000)


网络异常,图片无法展示
|


上面版本存在的问题是不能链式调用。


版本三


  • then方法直接返回promise。


  • 只有当then中的方法抛出异常的时候,他才会调用reject。否则都将调用resolve。


function execFunctionWithCatchError(execFn, value, resolve, reject) {
      try {
        const result = execFn(value)
        resolve(result)
      } catch(err) {
        reject(err)
      }
    }


const PROMISE_STATUS_PENDING = 'pending'
    const PROMISE_STATUS_FULFILLED = 'fulfilled'
    const PROMISE_STATUS_REJECTED = 'rejected'
    // 工具函数
    function execFunctionWithCatchError(execFn, value, resolve, reject) {
      try {
        const result = execFn(value)
        resolve(result)
      } catch(err) {
        reject(err)
      }
    }
    class MyPromise {
      constructor(executor) {
        this.status = PROMISE_STATUS_PENDING
        this.value = undefined
        this.reason = undefined
        this.onFulfilledFns = []
        this.onRejectedFns = []
        const resolve = (value) => {
          if (this.status === PROMISE_STATUS_PENDING) {
            // 添加微任务
            queueMicrotask(() => {
              if (this.status !== PROMISE_STATUS_PENDING) return
              this.status = PROMISE_STATUS_FULFILLED
              this.value = value
              this.onFulfilledFns.forEach(fn => {
                fn(this.value)
              })
            });
          }
        }
        const reject = (reason) => {
          if (this.status === PROMISE_STATUS_PENDING) {
            // 添加微任务
            queueMicrotask(() => {
              if (this.status !== PROMISE_STATUS_PENDING) return
              this.status = PROMISE_STATUS_REJECTED
              this.reason = reason
              this.onRejectedFns.forEach(fn => {
                fn(this.reason)
              })
            })
          }
        }
        try {
          executor(resolve, reject)
        } catch (err) {
          reject(err)
        }
      }
      then(onFulfilled, onRejected) {
        return new HYPromise((resolve, reject) => {
          // 如果在then调用的时候, 状态已经确定下来
          if (this.status === PROMISE_STATUS_FULFILLED && onFulfilled) {
            // try {
            //   const value = onFulfilled(this.value)
            //   resolve(value)
            // } catch(err) {
            //   reject(err)
            // }
            execFunctionWithCatchError(onFulfilled, this.value, resolve, reject)
          }
          if (this.status === PROMISE_STATUS_REJECTED && onRejected) {
            execFunctionWithCatchError(onRejected, this.reason, resolve, reject)
          }
          // 将成功回调和失败的回调放到数组中
          if (this.status === PROMISE_STATUS_PENDING) {
            // 这里是为了拿到返回的值所以再次封装一层函数
            this.onFulfilledFns.push(() => {
              execFunctionWithCatchError(onFulfilled, this.value, resolve, reject)
            })
            this.onRejectedFns.push(() => {
              execFunctionWithCatchError(onRejected, this.reason, resolve, reject)
            })
          }
        })
      }
    }


测试四


const promise = new MyPromise((resolve, reject) => {
      console.log("状态pending")
      resolve(1111) 
    })
    // 调用then方法多次调用
    promise.then(res => {
      console.log("res1:", res)
      return "aaaa"
    }, err => {
      console.log("err1:", err)
      return "bbbbb"
    }).then(res => {
      console.log("res2:", res)
    }, err => {
      console.log("err2:", err)
    })


网络异常,图片无法展示
|


上面的then方法没有判断then方法返回值,只是返回普通的值。


catch方法的设计


  • 利用then方法链式调用,抛出异常时,直接调用下一个then方法的onRejected。


  • 直接定义一个默认的onRejected方法,抛出错误。


  • 直接调用then方法的第二个参数即可


then(onFulfilled, onRejected) {
    // then方法新增
    + const defaultOnRejected = err => { throw err }
    + onRejected = onRejected || defaultOnRejected
  }
  catch(onRejected) {
    // 让其可以链式调用
    return this.then(undefined, onRejected)
  }


测试


const promise = new MyPromise((resolve, reject) => {
      console.log("状态pending")
      reject(2222)
    })
    // 调用then方法多次调用
    promise.then(res => {
      console.log("res:", res)
    }).catch(err => {
      console.log("err:", err)
    })


网络异常,图片无法展示
|


finally方法的设计


  • 实现原理其实和catch差不多。


  • 就是使用then方法的链式调用,在下一个then方法中的参数中执行finally参数。


then(onFulfilled, onRejected) {
    // then方法新增
    + const defaultOnFulfilled = value => { return value }
    + onFulfilled = onFulfilled || defaultOnFulfilled
  }
  finally(onFinally) {
    this.then(() => {
      onFinally()
    }, () => {
      onFinally()
    })
  }


测试


const promise = new MyPromise((resolve, reject) => {
      console.log("状态pending")
      resolve(1111)
    })
    // 调用then方法多次调用
    promise.then(res => {
      console.log("res1:", res)
      return "aaaaa"
    }).then(res => {
      console.log("res2:", res)
    }).catch(err => {
      console.log("err:", err)
    }).finally(() => {
      console.log("finally")
    })


网络异常,图片无法展示
|


resolve, reject方法设计


  • 直接调用Promise即可。然后再调用相应的resolve, reject方法。


static resolve(value) {
    return new MyPromise((resolve) => resolve(value))
  }
  static reject(reason) {
    return new MyPromise((resolve, reject) => reject(reason))
  }


测试


MyPromise.resolve("success=========").then(res => {
      console.log("res:", res)
    })
    MyPromise.reject("error========").catch(err => {
      console.log("err:", err)
    })


网络异常,图片无法展示
|


all方法的设计


  • 实现了上面的方法,对于all方法的实现就很容易了。主要弄清楚all方法的使用即可。


  • 当传入的promises数组中全部promise状态都为fulfilled时,才会去调用Promise的resolve,并将其promises的结果放在数组中返回。


  • 当传入的promises数组中有一个promise状态变为rejected时,就调用Promise的reject。


static all(promises) {
    return new MyPromise((resolve, reject) => {
      const values = []
      promises.forEach(promise => {
        promise.then(res => {
          values.push(res)
          if (values.length === promises.length) {
            resolve(values)
          }
        }, err => {
          reject(err)
        })
      })
    })
  }


测试


const p1 = new MyPromise((resolve) => {
      setTimeout(() => { resolve(1111) }, 1000)
    })
    const p2 = new MyPromise((resolve, reject) => {
      setTimeout(() => { reject(2222) }, 2000)
    })
    const p3 = new MyPromise((resolve) => {
      setTimeout(() => { resolve(3333) }, 3000)
    })
    MyPromise.all([p1, p2, p3]).then(res => {
      console.log(res)
    }).catch(err => {
      console.log(err)
    })


网络异常,图片无法展示
|


allSettled方法的设计


  • 这个方法主要是弥补all的缺陷的。


  • 不管传入的promises中的promise状态变成什么,他都会放入数组,然后等到所有状态都改变后,调用Promise的resolve方法。


  • 注意:他将传递该promise的状态和值。


static allSettled(promises) {
    return new MyPromise((resolve) => {
      const results = []
      promises.forEach(promise => {
        promise.then(res => {
          results.push({ status: PROMISE_STATUS_FULFILLED, value: res})
          if (results.length === promises.length) {
            resolve(results)
          }
        }, err => {
          results.push({ status: PROMISE_STATUS_REJECTED, value: err})
          if (results.length === promises.length) {
            resolve(results)
          }
        })
      })
    })
  }


测试


const p1 = new MyPromise((resolve) => {
      setTimeout(() => { resolve(1111) }, 1000)
    })
    const p2 = new MyPromise((resolve, reject) => {
      setTimeout(() => { reject(2222) }, 2000)
    })
    const p3 = new MyPromise((resolve) => {
      setTimeout(() => { resolve(3333) }, 3000)
    })
    MyPromise.allSettled([p1, p2, p3]).then(res => {
      console.log(res)
    })


网络异常,图片无法展示
|


race方法的设计


  • 这个方法主要是测试哪一个promise的状态先变化,就决定Promise的状态。


static race(promises) {
    return new HYPromise((resolve, reject) => {
      promises.forEach(promise => {
        // promise.then(res => {
        //   resolve(res)
        // }, err => {
        //   reject(err)
        // })
        promise.then(resolve, reject)
      })
    })
  } 


测试


const p1 = new MyPromise((resolve, reject) => {
  setTimeout(() => { reject(1111) }, 3000)
})
const p2 = new MyPromise((resolve, reject) => {
  setTimeout(() => { reject(2222) }, 2000)
})
const p3 = new MyPromise((resolve, reject) => {
  setTimeout(() => { reject(3333) }, 3000)
})
MyPromise.race([p1, p2, p3]).then(res => {
  console.log("res:", res)
}).catch(err => {
  console.log("err:", err)
})


网络异常,图片无法展示
|


any方法的设计


  • any方法会等到一个fulfilled状态,才会决定新Promise的状态。 如果所有的Promise都是reject的,那么也会等到所有的Promise都变成rejected状态。


  • 如果所有的Promise都是reject的,那么会报一个AggregateError的错误。


网络异常,图片无法展示
|


  • 通过err.errors可以拿到全部的reject时传递的参数。


  • 其实就是allSettled相反的思想。


static any(promises) {
    const reasons = []
    return new HYPromise((resolve, reject) => {
      promises.forEach(promise => {
        promise.then(resolve, err => {
          reasons.push(err)
          if (reasons.length === promises.length) {
            reject(new AggregateError(reasons))
          }
        })
      })
    })
  }


测试


MyPromise.any([p1, p2, p3]).then(res => {
      console.log("res:", res)
    }).catch(err => {
      console.log("err:", err.errors)
    })


网络异常,图片无法展示
|


以上就是手写Promise的所有思路,主要的实现还是搞清楚then方法的设计,搞定他,就搞定了Promise的90%了。其他方法就是理解使用就可以实现了。


珠峰架构公开课中的手写Promise


他的实现思路和coderwhy老师的不一样,我个人觉得他的思想思路比较容易理解。 主要设计


  • resolvePromise主要是对then方法返回值的判断。


  • then中的setTimeout是为了拿到then方法返回的Promise。


class Promise{
  constructor(executor){
    this.state = 'pending';
    this.value = undefined;
    this.reason = undefined;
    this.onResolvedCallbacks = [];
    this.onRejectedCallbacks = [];
    let resolve = value => {
      if (this.state === 'pending') {
        this.state = 'fulfilled';
        this.value = value;
        this.onResolvedCallbacks.forEach(fn=>fn());
      }
    };
    let reject = reason => {
      if (this.state === 'pending') {
        this.state = 'rejected';
        this.reason = reason;
        this.onRejectedCallbacks.forEach(fn=>fn());
      }
    };
    try{
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }
  then(onFulfilled,onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
    let promise2 = new Promise((resolve, reject) => {
      if (this.state === 'fulfilled') {
        setTimeout(() => {
          try {
            let x = onFulfilled(this.value);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      };
      if (this.state === 'rejected') {
        setTimeout(() => {
          try {
            let x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      };
      if (this.state === 'pending') {
        this.onResolvedCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onFulfilled(this.value);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              reject(e);
            }
          }, 0);
        });
        this.onRejectedCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onRejected(this.reason);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              reject(e);
            }
          }, 0)
        });
      };
    });
    return promise2;
  }
}
function resolvePromise(promise2, x, resolve, reject){
  if(x === promise2){
    return reject(new TypeError('Chaining cycle detected for promise'));
  }
  let called;
  if (x != null && (typeof x === 'object' || typeof x === 'function')) {
    try {
      let then = x.then;
      if (typeof then === 'function') { 
        then.call(x, y => {
          if(called)return;
          called = true;
          resolvePromise(promise2, y, resolve, reject);
        }, err => {
          if(called)return;
          called = true;
          reject(err);
        })
      } else {
        resolve(x);
      }
    } catch (e) {
      if(called)return;
      called = true;
      reject(e); 
    }
  } else {
    resolve(x);
  }
}


相关文章
|
13天前
|
前端开发 JavaScript
用JavaScript 实现一个简单的 Promise 并打印结果
用 JavaScript 实现一个简单的 Promise 并打印结果
|
13天前
|
JSON 前端开发 JavaScript
在 JavaScript 中,如何使用 Promise 处理异步操作?
通过以上方式,可以使用Promise来有效地处理各种异步操作,使异步代码更加清晰、易读和易于维护,避免了回调地狱的问题,提高了代码的质量和可维护性。
|
18天前
|
JSON 前端开发 JavaScript
浅谈JavaScript中的Promise、Async和Await
【10月更文挑战第30天】Promise、Async和Await是JavaScript中强大的异步编程工具,它们各自具有独特的优势和适用场景,开发者可以根据具体的项目需求和代码风格选择合适的方式来处理异步操作,从而编写出更加高效、可读和易于维护的JavaScript代码。
22 1
|
1月前
|
JavaScript 前端开发
JavaScript 函数语法
JavaScript 函数是使用 `function` 关键词定义的代码块,可在调用时执行特定任务。函数可以无参或带参,参数用于传递值并在函数内部使用。函数调用可在事件触发时进行,如用户点击按钮。JavaScript 对大小写敏感,函数名和关键词必须严格匹配。示例中展示了如何通过不同参数调用函数以生成不同的输出。
|
1月前
|
前端开发 JavaScript 开发者
JavaScript 中的异步编程:深入了解 Promise 和 async/await
【10月更文挑战第8天】JavaScript 中的异步编程:深入了解 Promise 和 async/await
|
1月前
|
前端开发 JavaScript 小程序
JavaScript的ES6中Promise的使用以及个人理解
JavaScript的ES6中Promise的使用以及个人理解
18 1
|
1月前
|
JavaScript 前端开发 大数据
在JavaScript中,Object.assign()方法或展开语法(...)来合并对象,Object.freeze()方法来冻结对象,防止对象被修改
在JavaScript中,Object.assign()方法或展开语法(...)来合并对象,Object.freeze()方法来冻结对象,防止对象被修改
18 0
|
1月前
|
前端开发 JavaScript UED
深入了解JavaScript异步编程:回调、Promise与async/await
【10月更文挑战第11天】深入了解JavaScript异步编程:回调、Promise与async/await
16 0
|
1月前
|
前端开发 JavaScript 开发者
深入理解JavaScript中的Promise:用法与最佳实践
【10月更文挑战第8天】深入理解JavaScript中的Promise:用法与最佳实践
58 0
|
6月前
|
前端开发 JavaScript
如何处理 JavaScript 中的异步操作和 Promise?
如何处理 JavaScript 中的异步操作和 Promise?
66 1
下一篇
无影云桌面