看了袁进老师的视频,我忍不住写篇文章跟着手写一遍promise
哈喽大家好,我是你们的金樽清酒。学习前端的小伙伴呀,在工作或面试的时候都逃不过一个东西,那就是promise,将异步代码变成同步代码。在没有promise之前都是通过回调函数来完成的,有了promise之后,就解决了回调地狱的问题,变成链式调用promise,使代码更加美观。要理解promise呢,最好手写一遍,才能理解透彻,今天就跟着我回顾一下吧。(这个手写promise是看袁进老师的视频学的)
prmiseA+规范
首先,怎么的promise才算promise呢?要搞懂这个问题,我们得看promise的A+规范。诶,只要符合了promsie的A+规范的那就是真正的promsie。要A+规范只要强调了一个什么事情呢?我们来看一下A+规范文档。 promisesaplus.com.cn/
这A+规范说了啥了。主要是强调了promise里面的then方法的实现。如果实现符合这个规范的话。那就是一个真正的promise。也就是说promise最重要的就是这个then方法的实现。满足这个规范呢。不同的promise函数之间都能进行操作,有一个互操性。你说这牛不牛逼。好了,话不多说,开写。
promise的构造器
分析一下promise,我们在用的时候是不是new 一个promise。执行一个回调,接受两个参数,resolve和reject。并且promsie有三个状态,fulfilled,pending,和rejected。resolve和reject会改变promise的状态,并且状态改变不可逆。
let a = new promise((resolve,reject)=>{
})
那我们写的promise的构造器要达到上述的效果。
class MyPromise{
static PENDING = 'penfing'
static FULFILLED ='fulfilled'
static REJRCTED = 'rejected'
#result = undefined
#state = MyPromise.PENDING
constructor(excutor){
const resolve = (result)=>{
this.#change(result,MyPromise.FULFILLED)
console.log(this.#result);
}
const reject = (reason)=>{
this.#change(reason,MyPromise.REJRCTED)
}
try{
excutor(resolve,reject)
}catch(error){
reject(error)
}
}
#change(data,status){
if(this.#state!== MyPromise.PENDING) return
this.#result = data
this.#state = status
}
}
首先我们先定义几个静态属性,用staic关键词修饰的可以被类直接用。这是防止硬编码。后面的状态不直接写死,由于用的地方比较多,如果后续要修改,则会改动很多地方。这是一个程序员应该想到的问题。
用#修饰的是私有属性,不会向外界暴露。change方法是我们抽离出来的函数。因为resolve和reject有很多相似的地方,所以封装一个change去调用。遵循dry原则,never repeat yourself。只有提高复用率,才能提高你的编程水平,写出好代码。
promise的then的返回值。
我们先看一下then的使用,它里面有两个回调参数,一个成功的回调,一个失败的回调。并且then能完成链式调用,说明它会返回一个promsie,而且我们能多次调用then方法,那我来写一下then方法吧。
先把上面的代码抄下来,现在我们要实现一个then方法们也就是A+规范中最重要的东西。
class MyPromise{
static PENDING = 'penfing'
static FULFILLED ='fulfilled'
static REJRCTED = 'rejected'
#result = undefined
#state = MyPromise.PENDING
#handler = []
constructor(excutor){
const resolve = (result)=>{
this.#change(result,MyPromise.FULFILLED)
console.log(this.#result);
}
const reject = (reason)=>{
this.#change(reason,MyPromise.REJRCTED)
}
try{
excutor(resolve,reject)
}catch(error){
reject(error)
}
}
#change(data,status){
if(this.#state!== MyPromise.PENDING) return
this.#result = data
this.#state = status
}
#run(){
if(this.#state === MyPromise.PENDING) return
while(this.#handler.length>0){
const { onFULfilled, onRejected, resolve, reject } = this.#handler.shift()
if(this.#state === MyPromise.FULFILLED){
//处于完成状态
if(typeof onFULfilled === 'function'){
onFULfilled()
}
}else{
if(typeof onFULfilled === 'function'){
onRejected()
}
}
}
}
then(onFULfilled,onRejected){
return new MyPromise((resolve,reject)=>{
//将要执行的函数收集起来,当状态发生变更的时候调用。
this.#handler.push({ onFULfilled, onRejected, resolve, reject});
this.#run()
})
}
}
为了解决多次调用then的问题,我们需要定义一个私有变量handler为一个数组。在调用then的时候把成功或失败的回调添加进数组,在状态发生变化的时候调用。我们用一个run方法来执行。
then中的promise的resolve和reject的执行时机
我怎么知道什么时候调用resolve,什么时候调用reject呢? 分为三种情况
第一种情况,在then中参数不是一个函数。这个时候就要透传,根据前面的状态判断是调用惹resolve还是reject。然后把前一个promise的值传入。
第二种情况 传入的是函数,且返回具体的值。用try,catch。没报错就调用resolve(),报错则调用reject。
第三种情况 传入的是函数,返回promise。则调用then方法。
我们复制一份上面的代码,在run函数里面进行判断。
class MyPromise{
static PENDING = 'penfing'
static FULFILLED ='fulfilled'
static REJRCTED = 'rejected'
#result = undefined
#state = MyPromise.PENDING
#handler = []
constructor(excutor){
const resolve = (result)=>{
this.#change(result,MyPromise.FULFILLED)
console.log(this.#result);
}
const reject = (reason)=>{
this.#change(reason,MyPromise.REJRCTED)
}
try{
excutor(resolve,reject)
}catch(error){
reject(error)
}
}
#change(data,status){
if(this.#state!== MyPromise.PENDING) return
this.#result = data
this.#state = status
}
#run(){
if(this.#state === MyPromise.PENDING) return
while(this.#handler.length>0){
const { onFULfilled, onRejected, resolve, reject } = this.#handler.shift()
if(this.#state === MyPromise.FULFILLED){
//处于完成状态
if(typeof onFULfilled === 'function'){
onFULfilled()
}else{
//回调不是函数,则进行透传
resolve(this.#result)
}
}else{
if(typeof onRejected === 'function'){
onRejected()
}else{
reject(this.#result)
}
}
}
}
then(onFULfilled,onRejected){
return new MyPromise((resolve,reject)=>{
//将要执行的函数收集起来,当状态发生变更的时候调用。
this.#handler.push({ onFULfilled, onRejected, resolve, reject});
this.#run()
})
}
可以看到我们这么写的话又有好多重复的逻辑。那我们又可以抽一个函数出来,判断什么时候调用resolve和reject。我们写一个runOne辅助函数。
class MyPromise{
static PENDING = 'penfing'
static FULFILLED ='fulfilled'
static REJRCTED = 'rejected'
#result = undefined
#state = MyPromise.PENDING
#handler = []
constructor(excutor){
const resolve = (result)=>{
this.#change(result,MyPromise.FULFILLED)
console.log(this.#result);
}
const reject = (reason)=>{
this.#change(reason,MyPromise.REJRCTED)
}
try{
excutor(resolve,reject)
}catch(error){
reject(error)
}
}
#change(data,status){
if(this.#state!== MyPromise.PENDING) return
this.#result = data
this.#state = status
}
#runOne(callBack, resolve, reject){
if (typeof callBack !== 'function') {
const settled = this.#state === MyPromise.FULFILLED ? resolve : reject
settled(this.#result)
}
else {
try {
const data = callBack(this.#result)
if (this.#isPromiseLike(data)) {
data.then(resolve, reject)
} else {
resolve(data)
}
} catch (error) {
reject(error)
}
}
}
#run(){
if(this.#state === MyPromise.PENDING) return
while (this.#handler.length > 0) {
const { onFULfilled, onRejected, resolve, reject } = this.#handler.shift()
if (this.#state === MyPromise.FULFILLED) {
this.#runOne(onFULfilled, resolve, reject)
} else {
onRejected(this.#result)
this.#runOne(onRejected, resolve, reject)
}
}
}
then(onFULfilled,onRejected){
return new MyPromise((resolve,reject)=>{
//将要执行的函数收集起来,当状态发生变更的时候调用。
this.#handler.push({ onFULfilled, onRejected, resolve, reject});
this.#run()
})
}
写到这呢,还没完,上面的代码不是有个isPromiseLike辅助函数判断是不是promise嘛。为啥不直接用data instancof MyPromise呢?这不行,这只能判断返回的是不是我们写的这个promise,而只要符合promiseA+规范的那就是promise。也就是说返回的是官方的promise和你写的promise都是promise,可以相互之间使用的,它是有互操性的。那我们来完善一下这个代码,看一下,什么条件是promiseA+规范的promise呢?复制一下上面的代码。
class MyPromise{
static PENDING = 'penfing'
static FULFILLED ='fulfilled'
static REJRCTED = 'rejected'
#result = undefined
#state = MyPromise.PENDING
#handler = []
constructor(excutor){
const resolve = (result)=>{
this.#change(result,MyPromise.FULFILLED)
console.log(this.#result);
}
const reject = (reason)=>{
this.#change(reason,MyPromise.REJRCTED)
}
try{
excutor(resolve,reject)
}catch(error){
reject(error)
}
}
#change(data,status){
if(this.#state!== MyPromise.PENDING) return
this.#result = data
this.#state = status
}
#isPromiseLike(value){
if (value !== null && (typeof value === 'object' || typeof value ==='function')) {
return typeof value.then === 'function'
} else {
return false
}
}
#runOne(callBack, resolve, reject){
if (typeof callBack !== 'function') {
const settled = this.#state === MyPromise.FULFILLED ? resolve : reject
settled(this.#result)
}
else {
try {
const data = callBack(this.#result)
if (this.#isPromiseLike(data)) {
data.then(resolve, reject)
} else {
resolve(data)
}
} catch (error) {
reject(error)
}
}
}
#run(){
if(this.#state === MyPromise.PENDING) return
while (this.#handler.length > 0) {
const { onFULfilled, onRejected, resolve, reject } = this.#handler.shift()
if (this.#state === MyPromise.FULFILLED) {
this.#runOne(onFULfilled, resolve, reject)
} else {
onRejected(this.#result)
this.#runOne(onRejected, resolve, reject)
}
}
}
then(onFULfilled,onRejected){
return new MyPromise((resolve,reject)=>{
//将要执行的函数收集起来,当状态发生变更的时候调用。
this.#handler.push({ onFULfilled, onRejected, resolve, reject});
this.#run()
})
}
主要是看里面有什么then方法。
如图,我们在自己写的Promise里面返回官方的promise,一样返回正确的结果。这就是互操性。只要符合promiseA+规范的就是promise。只不过官方还封装了很多方法,但是,难道没有那些方法我这就不叫promise了嘛。九二爷比你多了学历你就不是程序员了嘛,显然不成立。
写到这就完了嘛,不,还少了一点点内容。微任务队列。还记得我们的then是微任务嘛。可是咱们现在是同步代码。那怎么办呢?我们需要根据环境模拟微任务,在then调用的时候使用。复制一下代码。
class MyPromise{
static PENDING = 'penfing'
static FULFILLED ='fulfilled'
static REJRCTED = 'rejected'
#result = undefined
#state = MyPromise.PENDING
#handler = []
constructor(excutor){
const resolve = (result)=>{
this.#change(result,MyPromise.FULFILLED)
console.log(this.#result);
}
const reject = (reason)=>{
this.#change(reason,MyPromise.REJRCTED)
}
try{
excutor(resolve,reject)
}catch(error){
reject(error)
}
}
#change(data,status){
if(this.#state!== MyPromise.PENDING) return
this.#result = data
this.#state = status
}
#runMicroTask(func) {//放在微任务当中运行
if (typeof process === 'object' && typeof process.nextTick === 'function') {
process.nextTick(func)
} else if (typeof MutationObserver === 'function') {
const ob = new MutationObserver(func)
const textNode = document.createTextNode('1')
ob.observe(textNode, {
characterData: true
})
textNode.data = '2'
}else{//脱离环境,就没法模拟微任务。事件循环是环境能力而不是语言能力。
setTimeout(func,0)
}
}
#isPromiseLike(value){
if (value !== null && (typeof value === 'object' || typeof value ==='function')) {
return typeof value.then === 'function'
} else {
return false
}
}
#runOne(callBack, resolve, reject){
this. #runMicroTask(()=>{
if (typeof callBack !== 'function') {
const settled = this.#state === MyPromise.FULFILLED ? resolve : reject
settled(this.#result)
}
else {
try {
const data = callBack(this.#result)
if (this.#isPromiseLike(data)) {
data.then(resolve, reject)
} else {
resolve(data)
}
} catch (error) {
reject(error)
}
}
})
}
#run(){
if(this.#state === MyPromise.PENDING) return
while (this.#handler.length > 0) {
const { onFULfilled, onRejected, resolve, reject } = this.#handler.shift()
if (this.#state === MyPromise.FULFILLED) {
this.#runOne(onFULfilled, resolve, reject)
} else {
this.#runOne(onRejected, resolve, reject)
}
}
}
then(onFULfilled,onRejected){
return new MyPromise((resolve,reject)=>{
//将要执行的函数收集起来,当状态发生变更的时候调用。
this.#handler.push({ onFULfilled, onRejected, resolve, reject});
this.#run()
})
}
runMicroTaskfu辅助方法判断是在node环境还是在浏览器环境进行模拟微任务。其他情况无法模拟微任务。因为事件循环是环境给予的能力,脱离环境就办不到了。
我们来验证一下
到这为止我们的promise大体也写完了。代码格式可能不太优雅,见谅。因为我复制过来没有格式。自己手动打的空格。也可能上述代码哪里出了纰漏因为上述代码没有测试。可以看下面这个完整吧,复制到编译器看。
class MyPromise {
static PENDING = 'Pending'
static FULFILLED = 'Fulfilled'
static REJRCTED = 'Rejected'
#result = undefined
#state = MyPromise.PENDING
#handler = []
constructor(excutor) {
const resolve = (result) => {
this.#change(result, MyPromise.FULFILLED)
}
const reject = (reason) => {
this.#change(reason, MyPromise.REJRCTED)
}
excutor(resolve, reject)
}
#isPrimiseLike(value) {//判断是不是一个promise,只需要符合A+规范就可以。
if (value !== null && (typeof value === 'object' || typeof value === 'function')) {
return typeof value.then === 'function'
} else {
return false
}
}
#runMicroTask(func) {//放在微任务当中运行
if (typeof process === 'object' && typeof process.nextTick === 'function') {
process.nextTick(func)
} else if (typeof MutationObserver === 'function') {
const ob = new MutationObserver(func)
const textNode = document.createTextNode('1')
ob.observe(textNode, {
characterData: true
})
textNode.data = '2'
}else{//脱离环境,就没法模拟微任务。事件循环是环境能力而不是语言能力。
setTimeout(func,0)
}
}
#change(data, status) {
if (this.#state !== MyPromise.PENDING) return
this.#result = data
this.#state = status
this.#run()
}
#runOne(callBack, resolve, reject) {
this.#runMicroTask(() => {
if (typeof callBack !== 'function') {
const settled = this.#state === MyPromise.FULFILLED ? resolve : reject
settled(this.#result)
} else {
try {
const data = callBack(this.#result)
if (this.#isPrimiseLike(data)) {
data.then(resolve, reject)
} else {
resolve(data)
}
} catch (error) {
reject(error)
}
}
})
}
#run() {
if (this.#state === MyPromise.PENDING) return
while (this.#handler.length > 0) {
const { onFULfilled, onRejected, resolve, reject } = this.#handler.shift()
if (this.#state === MyPromise.FULFILLED) {
this.#runOne(onFULfilled, resolve, reject)
} else {
this.#runOne(onRejected, resolve, reject)
}
}
}
then(onFULfilled, onRejected) {
return new MyPromise((resolve, reject) => {
//将要执行的函数收集起来,当状态发生变更的时候调用。
this.#handler.push({ onFULfilled, onRejected, resolve, reject });
this.#run()
})
}
}