手書きPromiseの基本実装(超詳細)

目次

1: まず公式の約束を分析する

2: 手書きの約束からメソッドの設計

3: 次にメソッドの最適化:

4: プロミス・キャッチ方式の設計

 5: Promise-finally メソッドの設計


//この記事では、基本的な約束を理解します。境界条件が多すぎることはテストではありません。主な実装プロセスとロジックを理解するだけです。

//次々に発生する問題について、原因とそれに対応する解決策を徐々に分析していきます

//前提として、Promise の基本的な使用法をマスターし、それに応じてコールバック関数を理解する必要があります。

//ステップ 1: 最も単純な Promise の暫定実装 (ステップごとに書き留めてください。Xiaobai はそれを理解できます。もちろん、いくつかのナンセンスを直接スキップすることもできます~~~)

1: まず公式の約束を分析する

//---首先分析官方的promise

const p1 = new Promise((resolve, reject) => {
    resolve(2222)
    reject(33333)
})
// 调用then方法 then 方法接收2个回调函数作为参数
p1.then(res => { //只会打印222, 因为promise的状态一但由pending的状态改变了 就无法再次改变
    console.log("res1:", res)
}, err => {
    console.log("err:", err)
})

//分析:

//promise のパラメータが関数を受け取り、その関数内のパラメータとして 2 つのコールバック関数を受け取り、resolve メソッドを呼び出していることがわかります。

// then メソッドを実行しています。then 関数は 2 つのパラメータを受け取りますが、どちらもコールバック関数であり、resolve(2222) を実行すると、then の最初のパラメータのコールバック関数に戻ります。

// request(33333) を実行した後、then- の第 2 パラメータのコールバック関数に移ります。

//1. 次に、予備的な書き込みを取得できます。

const PROMISE_STATUS_PENDING = 'pending' //定义三种状态常量
const PROMISE_STATUS_FULFILLED = 'fulfilled'
const PROMISE_STATUS_REJECTED = 'rejected'


class myPromise {
    constructor(executor) {
        this.status = PROMISE_STATUS_PENDING //1.初始化状态 pending
        this.value = undefined //2.保存参数
        this.error = undefined
        const resolve = ((value) => {
            if (this.status === PROMISE_STATUS_PENDING) { //3. 只有pending状态才可改变
                this.status = PROMISE_STATUS_FULFILLED
            }
        })


        const reject = ((error) => {
            if (this.status === PROMISE_STATUS_PENDING) {
                this.status = PROMISE_STATUS_REJECTED


            }
        })


        executor(resolve, reject) //在new myPromise 时初始化立即调用传递过来的函数
    }
}

2: 手書きの約束からメソッドの設計

//2. 解決または拒否を実行すると、then メソッドが呼び出されます (上記のコードに基づいて変更されました)

class myPromise {
    constructor(executor) {


        this.status = PROMISE_STATUS_PENDING
        this.value = undefined
        this.error = undefined
        const resolve = ((value) => {
            if (this.status === PROMISE_STATUS_PENDING) {
                this.status = PROMISE_STATUS_FULFILLED


                this.resfn(value)
            }
        })


        const reject = ((error) => {
            if (this.status === PROMISE_STATUS_PENDING) {
                this.status = PROMISE_STATUS_REJECTED


                this.errfn(error)
            }
        })


        executor(resolve, reject)
    }


    then(resfn, errfn) { //4.then 方法接收2个回调函数作为 参数 : 第一个为成功的回调,第二个失败的回调
        this.resfn = resfn
        this.errfn = errfn
    }
}

//(コードが最も基本的な実装であるかどうかを確認します) 次に、実装した Promise を実行して、それが正常であるかどうかを確認します

 const p1 = new myPromise((解決、拒否) => {

    解決(111)

})

p1.then(res => {

console.log(res);

}、エラー => {

})

//実行後、エラー メッセージが表示されます: this.resfn は関数ではありません

//それでは、なぜエラーが報告されるのでしょうか? コードの実行順序に関係するので、new myPromise()でresolveを実行するとresolveメソッドにたどり着きます。

//そして、その後のメソッド アヒルのスープは実行されないため、エラーが報告されます

//解決策: then を最初に実行させ、then メソッドが実行されるときに非同期キューに replace または拒否を追加します。

上記のコード:


class myPromise {
    constructor(executor) {
        this.status = PROMISE_STATUS_PENDING
        this.value = undefined
        this.error = undefined
        const resolve = ((value) => {
            if (this.status === PROMISE_STATUS_PENDING) {
                this.status = PROMISE_STATUS_FULFILLED
                //大家可能会想到使用setTimeout 然后0秒执行,但此处使用queueMicrotask 会更加合适
                // setTimeout(() => {
                //     this.resfn(value)
                // }, 0);

                queueMicrotask(() => { //queueMicrotask:  主线程执行完毕之后立马执行
                    this.resfn(value)
                })
            }
        })

        const reject = ((error) => {
            if (this.status === PROMISE_STATUS_PENDING) {
                this.status = PROMISE_STATUS_REJECTED
                queueMicrotask(() => {
                    this.errfn(error)
                })
            }
        })
        executor(resolve, reject)
    }

    then(resfn, errfn) { //4.then 方法接收2个回调函数作为 参数 : 第一个为成功的回调,第二个失败的回调
        this.resfn = resfn
        this.errfn = errfn
    }
}


//举一个栗子:
setTimeout(() => {
    console.log('setTimeout');
}, 0);


queueMicrotask(() => {
    console.log('queueMicrotask');
});


//实际运行
queueMicrotask
setTimeout

次に、以下を実行します。

const p1 = new myPromise((解決、拒否) => {

    解決(111)

    拒否(333333)

})

p1.then(res => { // 最終出力 1111

    console.log(res);

}、エラー => {

    コンソール.ログ(エラー);

})

これまでのところ、最も単純な実装は完了しています。その後、 then メソッドの最適化を続けます

最適化 1:
(公式の Promise は複数回呼び出すことができます)
// mypromise の独自の実装では then メソッドを複数回呼び出しますが、現在サポートされていません 
p1.then(res => {
    console.log("res1:", res)
}、エラー => {
    console.log("err1:", err)
})
// then メソッドを呼び出して複数回呼び出す
p1.then(res => {
    console.log("res2:", res)
}、エラー => {
    console.log("err2:", err)
})
実行結果: res2: 111 次の .then は前のコードを上書きするため、res1 が配置されているコード ブロックは実行されません。
* then メソッドが呼び出されるとき、それは配列であり、順番に呼び出されることがわかります。
以下のコードを変更してみましょう。
上記のコード:
class myPromise {
    constructor(executor) {
        this.status = PROMISE_STATUS_PENDING
        this.value = undefined
        this.error = undefined

        this.resfns = [] //1.多次调用then 时用数组 保存
        this.errfns = []

        const resolve = ((value) => {
            if (this.status === PROMISE_STATUS_PENDING) {
                this.status = PROMISE_STATUS_FULFILLED
                queueMicrotask(() => {
                    this.value = value
                    this.resfns.forEach(fn => {        //循环依次调用
                        fn(this.value)
                    })
                })
            }
        })

        const reject = ((error) => {
            if (this.status === PROMISE_STATUS_PENDING) {
                this.status = PROMISE_STATUS_REJECTED
                queueMicrotask(() => {
                    this.error = error
                    this.errfns.forEach(fn => {
                        fn(this.error)
                    })
                })
            }
        })
        executor(resolve, reject)
    }

    then(resfn, errfn) {        //将多次调用的then 方法保存到数组中,依次调用
        this.resfns.push(resfn)
        this.errfns.push(errfn)
    }
}
次に、以下を実行します。
const p1 = new myPromise((解決、拒否) => {
    解決(111)
    拒否(333333)
})
p1.then(res => {
    console.log("res1:", res)
}、エラー => {
    console.log("err1:", err)
})
// then メソッドを呼び出して複数回呼び出す
p1.then(res => {
    console.log("res2:", res)
}、エラー => {
    console.log("err2:", err)
})
結果:
レス1: 111
レス2: 111
これで複数の呼び出しの問題は解決しましたか。コードを注意深く見てください。
解決関数では、ステータスが保留中の場合にのみ then コールバック関数を実行します。
それでは、実行時にステータスが保留中でなくなったら、実行されないのでしょうか?
すると友人の中には、「実行中にどうやってその状態が変わったのでしょうか?あなたが書いたコードが成功コールバックと失敗コールバックを配列にプッシュしているのがはっきりとわかりました。そしてそれを順番に実行するのですか?」と尋ねる人もいるかもしれません。
もっと抽象的かもしれません: 次に、コードを使用してこの問題を再現します。
const Promise = new HYPromise((解決、拒否) => {
  console.log("ステータス保留中")
  解決(1111) 
  拒否(2222)
})
// then メソッドを呼び出して複数回呼び出す
約束.then(res => {
  console.log("res1:", res)
}、エラー => {
  console.log("エラー:", エラー)
})
約束.then(res => {
  console.log("res2:", res)
}、エラー => {
  console.log("err2:", err)
})
/** ここをよく見てください。上記のコードは同期です。実行は対応する配列に追加され、順番に呼び出されます。呼び出しが完了した後、状態は決定されますか? */
//次に、以下の非同期コードを実行します。状態は変更されているため、この時点では何も実行されません。  この関数はすでに this.resfns.push(resfn) がありますが、まったく実行されません。
setTimeout(() => {
  約束.then(res => {
    console.log("res3:", res)
  }、エラー => {
    console.log("err3:", err)
  })
}、1000)
結果:
レス1: 111
レス2: 111
3番目は実行されないことがわかります
解決策:
then が呼び出されたときに状態が決定されている場合は、 then のコールバックを直接実行する必要があります。
上記のコード:
class myPromise {
    constructor(executor) {
        this.status = PROMISE_STATUS_PENDING
        this.value = undefined
        this.error = undefined

        this.resfns = [] //1.多次调用then 时用数组 保存
        this.errfns = []

        const resolve = ((value) => {
            if (this.status === PROMISE_STATUS_PENDING) {
                queueMicrotask(() => {
                    this.status = PROMISE_STATUS_FULFILLED
                    this.value = value
                    this.resfns.forEach(fn => {
                   
                        fn(this.value)
                    })
                })
            }
        })


        const reject = ((error) => {
            if (this.status === PROMISE_STATUS_PENDING) {
                queueMicrotask(() => {
                    this.status = PROMISE_STATUS_REJECTED
                    this.error = error
                    this.errfns.forEach(fn => {
                        fn(this.error)
                    })
                })
            }
        })

        executor(resolve, reject)
    }

    then(resfn, errfn) {
        // 1.如果在then调用的时候, 状态已经确定下来,那么就可以直接调用
        if (this.status === PROMISE_STATUS_FULFILLED && resfn) {
            resfn(this.value)
        }
        if (this.status === PROMISE_STATUS_REJECTED && errfn) {
            errfn(this.error)
        }

        //2. 相反 对与已经确定的状态 就不需要在push 执行了
        if (this.status === PROMISE_STATUS_PENDING) {
            this.resfns.push(resfn)
            this.errfns.push(errfn)
        }

    }
}

埋め込む:

const p1 = new myPromise((解決、拒否) => {

    解決(111)

    // 拒否(333333)

})

p1.then(res => {

    console.log("res1:", res)

}、エラー => {

    console.log("err1:", err)

})

// then メソッドを呼び出して複数回呼び出す

p1.then(res => {

    console.log("res2:", res)

}、エラー => {

    console.log("err2:", err)

})

// Promise の状態を決定した後、再度呼び出します

setTimeout(() => {

    p1.then(res => {

        console.log("res3:", res)

    }、エラー => {

        console.log("err3:", err)

    })

}、1000)

結果:

レス1: 111

レス2: 111

レス3:111

3: 次にメソッドの最適化:

前の then メソッドでは、インスタンス化されたクラスを通じて複数の呼び出しが呼び出されます。

例えば:

p1.then(res => {

    console.log("res1:", res)

}、エラー => {

    console.log("err1:", err)

})

p1.then(res => {

    console.log("res2:", res)

}、エラー => {

    console.log("err2:", err)

})

この種の呼び出しは現在のコードで実現可能ですが、常にチェーンで呼び出される場合はどうなるでしょうか? くだらない話をしてコードに進みましょう。

const p1 = new myPromise((解決、拒否) => {

    解決(111)

    拒否(333333)

})

p1.then(res => {

    console.log("最初に成功したコールバック:", res)

    「2 回目の成功コールバック」を返す

}、エラー => {

    console.log("err1:", err)

    return "2 番目の失敗コールバック"

}).then(res => {

    console.log("res2:", res)

}、エラー => {

    console.log("err2:", err)

})

これで、私たちのコードは間違いなく機能しません (独自の環境でテストできます) 次に、コードの最適化を続けます。

最適化する前に、まず Wave を分析します (公式の Promise チェーン コールを例として使用して説明します)。

1.解決を実行する 

2. then メソッドを呼び出します。 then メソッドによって返される値は実際には new myPromse((resolve,reject) =>solve("The 2 番目に成功したコールバック")) であり、戻り値はsolve() メソッドで渡されます。

したがって、「最初に成功したコールバック:」を出力した後、111、2 番目に成功したコールバックである「res2:」を再度出力します。

3. それでは、「err2:」の 2 番目の失敗コールバックはいつ出力されるのでしょうか? 初めて then を呼び出すときに例外をスローするだけでよく、err2 が実行されます。

例えば:

p1.then(res => {

    console.log("最初に成功したコールバック:", res)

    throw new Error("エラーメッセージ")

}、エラー => {

    console.log("err1:", err)

    return "2 番目の失敗コールバック"

}).then(res => {

    console.log("res2:", res)

}、エラー => {

    console.log("err2:", err)

})

結果:

"最初に成功したコールバック:", 111

「err2: エラー: エラーメッセージ

2. 拒否が実行される場合、拒否の 2 番目のコールバックで 

エラー => {

    console.log("err1:", err)

    return "2 番目の失敗コールバック"

}

 値が返された場合、次に console.log("res2:", res) または console.log("err2:", err) が実行されますか?

例えば:

const p1 = new myPromise((解決、拒否) => {

    拒否(333333)

})

p1.then(res => {

    console.log("最初に成功したコールバック:", res)

    「2 回目の成功コールバック」を返す

}、エラー => {

    console.log("err1:", err)

    return "2 番目の失敗コールバック"

}).then(res => {

    console.log("res2:", res)

}、エラー => {

    console.log("err2:", err)

})

ここでは、console.log("res2:", res) が実際に実行されることを説明します。console.log("err2:", err) でロックされたコード ブロックを実行したい場合は、「the」を返す必要があります。 2 番目の失敗コールバック「実行するために例外をスローするように変更する」

栗:

p1.then(res => {

    console.log("最初に成功したコールバック:", res)

    「2 回目の成功コールバック」を返す

}、エラー => {

    console.log("err1:", err)

   throw new Error("エラーメッセージ")

}).then(res => {

    console.log("res2:", res)

}、エラー => {

    console.log("err2:", err)

})

ロジックは明確化され、コードで表現されています: (次のコードをコピーし、上記のケースで自分でテストしてより明確にすることができます)

class myPromise {
    constructor(executor) {
        this.status = PROMISE_STATUS_PENDING
        this.value = undefined
        this.error = undefined
        this.resfns = []
        this.errfns = []
        const resolve = ((value) => {
            if (this.status === PROMISE_STATUS_PENDING) {
                queueMicrotask(() => {
                    if (this.status !== PROMISE_STATUS_PENDING) return //避免 调用resolve 后又调用 reject 多次执行
                    this.status = PROMISE_STATUS_FULFILLED
                    this.value = value
                    this.resfns.forEach(fn => {
                        fn(this.value)
                    })
                })
            }
        })

        const reject = ((error) => {
            if (this.status === PROMISE_STATUS_PENDING) {
                queueMicrotask(() => {
                    if (this.status !== PROMISE_STATUS_PENDING) return
                    this.status = PROMISE_STATUS_REJECTED
                    this.error = error
                    this.errfns.forEach(fn => {
                        fn(this.error)
                    })
                })
            }
        })
        executor(resolve, reject)
    }


    then(resfn, errfn) {
        return new myPromise((resolve, reject) => { //1. 直接new 一个mypromise 作为then 方法的返回值 既可实现 .then.then.thne.then.....等等链式调用
            if (this.status === PROMISE_STATUS_FULFILLED && resfn) {
                try { //2. 异常处理:若成功则继续执行then链式调用 的第一个回调,失败则执行then 的第二个回调
                    const value = resfn(this.value)
                    resolve(value)
                } catch (err) {
                    reject(err)
                }
            }


            if (this.status === PROMISE_STATUS_REJECTED && errfn) {
                try {
                    const value = errfn(this.error)
                    resolve(value)
                } catch (err) {
                    reject(err)
                }
            }


            if (this.status === PROMISE_STATUS_PENDING) {
                this.resfns.push(() => { //push 回调函数
                    try {
                        const value = resfn(this.value)
                        resolve(value)
                    } catch (err) {
                        reject(err)
                    }
                })
                this.errfns.push(() => {
                    try {
                        const value = errfn(this.error)
                        resolve(value)
                    } catch (err) {
                        reject(err)
                    }
                })
            }
        })
    }
}

//然后测试:
const p1 = new myPromise((resolve, reject) => {
    // resolve(111)
    reject(333333)
})

p1.then(res => {
    console.log("res1:", res)
    return "第二次的成功回调"
}, err => {
    console.log("err1:", err)
     throw new Error("err message")
   // return "第二次的失败回调"
}).then(res => {
    console.log("res2:", res)
}, err => {
    console.log("err2:", err)
})
結果:
エラー1: 333333
err2: エラー: エラーメッセージ
OK、完璧な解決策 

4: プロミス・キャッチ方式の設計

上記のコードで基本的な Promise を実装しました。
この時点で、catch メソッドの設定は非常に簡単です。
上記のコード:
class myPromise {
    constructor(executor) {
        this.status = PROMISE_STATUS_PENDING
        this.value = undefined
        this.error = undefined
        this.resfns = []
        this.errfns = []

        const resolve = ((value) => {
            if (this.status === PROMISE_STATUS_PENDING) {
                queueMicrotask(() => {
                    if (this.status !== PROMISE_STATUS_PENDING) return //避免 调用resolve 后又调用 reject 多次执行
                    this.status = PROMISE_STATUS_FULFILLED
                    this.value = value
                    this.resfns.forEach(fn => {
                        fn(this.value)
                    })
                })
            }
        })

        const reject = ((error) => {
            if (this.status === PROMISE_STATUS_PENDING) {
                queueMicrotask(() => {
                    if (this.status !== PROMISE_STATUS_PENDING) return
                    this.status = PROMISE_STATUS_REJECTED
                    this.error = error
                    this.errfns.forEach(fn => {
                        fn(this.error)
                    })
                })
            }
        })
        executor(resolve, reject)
    }


    then(resfn, errfn) {
        // 1.难点:利用抛错让下一个promise的catch帮忙处理  防止catch方法让链式调用断层
        const defaultOnRejected = err => {
            throw err
        }
        errfn = errfn || defaultOnRejected


        return new myPromise((resolve, reject) => {


            if (this.status === PROMISE_STATUS_FULFILLED && resfn) {
                try { //2. 异常处理:若成功则继续执行then链式调用 的第一个回调,失败则执行then 的第二个回调
                    const value = resfn(this.value)
                    resolve(value)
                } catch (err) {
                    reject(err)
                }
            }
            if (this.status === PROMISE_STATUS_REJECTED && errfn) {
                try {
                    const value = errfn(this.error)
                    resolve(value)
                } catch (err) {
                    reject(err)
                }
            }


            if (this.status === PROMISE_STATUS_PENDING) {
                if (resfn) {
                    this.resfns.push(() => { //push 回调函数
                        try {
                            const value = resfn(this.value)
                            resolve(value)
                        } catch (err) {
                            reject(err)     //tips:****利用抛出的错误 使得下一个promise的catch帮忙处理
                        }
                    })
                }
                if (errfn) {
                    this.errfns.push(() => {
                        try {
                            const value = errfn(this.error)
                            resolve(value)
                        } catch (err) {
                            reject(err)
                        }
                    })
                }
            }
        })
    }
    catch (errfn) { //2.catch 方法
        return this.then(undefined, errfn)
    }
}

//接着测试代码:
const p1 = new myPromise((resolve, reject) => {
    reject(333333)
})

p1.then(res => {
    console.log("res:", res)
}).catch(err => {
    console.log("err:", err)
})

//结果:
err: 333333
ここでの難しい点は、最初の p1.then のコールバックでパラメータが 1 つだけ渡される場合に、デフォルトの関数を別のパラメータに割り当てることです。 
そして、この関数は、マークされたヒント コードのキャッチによってキャッチされる例外をスローする必要があります。その後、reject(err) を使用できます。catch
もちろん電話したい場合は
p1.catch(err => {
    console.log("エラー:", エラー)
}).then(res => {
    console.log("res:", res)
})
デフォルトの関数を再割り当てするだけで、コード ブロックが try 受信時に解決(値)できるようになります。
このコード行をコメントの最初の行に追加するだけです
const defaultOnFulfilled = 値 => {
            戻り値
        }
resfn = resfn || デフォルトオンフルフィルド

 5: Promise-finally メソッドの設計

//finally 関数は成功または失敗に関係なく呼び出され、最後に呼び出されます。
//finally メソッドをクラスに直接追加して、 queueMicrotask のマイクロタスクの最速処理を活用し、final(fn) によって渡された関数 fn が最終的に実行されるようにします。
  最後に(fn) {
        setTimeout(() => {
            fn()
        }、0)
    }
最後にすべてのコードを添付します。
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.error = undefined
        this.resfns = []
        this.errfns = []

        const resolve = ((value) => {
            if (this.status === PROMISE_STATUS_PENDING) {
                queueMicrotask(() => {
                    if (this.status !== PROMISE_STATUS_PENDING) return //避免 调用resolve 后又调用 reject 多次执行
                    this.status = PROMISE_STATUS_FULFILLED
                    this.value = value
                    this.resfns.forEach(fn => {
                        fn(this.value)
                    })
                })
            }
        })
        const reject = ((error) => {
            if (this.status === PROMISE_STATUS_PENDING) {
                queueMicrotask(() => {
                    if (this.status !== PROMISE_STATUS_PENDING) return
                    this.status = PROMISE_STATUS_REJECTED
                    this.error = error
                    this.errfns.forEach(fn => {
                        fn(this.error)
                    })
                })
            }
        })
        executor(resolve, reject)
    }


    then(resfn, errfn) {
        // 1.利用抛错让下一个promise的catch帮忙处理  防止catch方法让链式调用断层
        const defaultOnRejected = err => {
            throw err
        }
        errfn = errfn || defaultOnRejected


        const defaultOnFulfilled = value => {
            return value
        }
        resfn = resfn || defaultOnFulfilled


        return new myPromise((resolve, reject) => { //1. 直接new 一个mypromise 作为then 方法的返回值 既可实现 .then.then.thne.then.....等等链式调用

            if (this.status === PROMISE_STATUS_FULFILLED && resfn) {
                try { //2. 异常处理:若成功则继续执行then链式调用 的第一个回调,失败则执行then 的第二个回调
                    const value = resfn(this.value)
                    resolve(value)
                } catch (err) {
                    reject(err)
                }
            }
            if (this.status === PROMISE_STATUS_REJECTED && errfn) {
                try {
                    const value = errfn(this.error)
                    resolve(value)
                } catch (err) {
                    reject(err)
                }
            }
            if (this.status === PROMISE_STATUS_PENDING) {
                if (resfn) {
                    this.resfns.push(() => { //push 回调函数
                        try {
                            const value = resfn(this.value)
                            resolve(value)
                        } catch (err) {
                            reject(err) //tips:****利用抛出的错误 使得下一个promise的catch帮忙处理
                        }
                    })
                }
                if (errfn) {
                    this.errfns.push(() => {
                        try {
                            const value = errfn(this.error)
                            resolve(value)
                        } catch (err) {
                            reject(err)
                        }
                    })
                }
            }
        })
    }

    catch (errfn) { //2.catch 方法
        return this.then(undefined, errfn)
    }
   
    finally(fn) {
        setTimeout(() => {
            fn()
        }, 0)
    }
}

残りは比較的単純なクラスメソッドですので、ここでは説明しません~~さようなら~

おすすめ

転載: blog.csdn.net/qq_48554377/article/details/126992966