Promiseを実装するための手書きJS

Guanhui
リリース: 2020-05-07 09:27:21
転載
2796 人が閲覧しました

Promise の概要

Promise は、非同期プログラミングを管理するためのソリューションです。毎回新しいインスタンスを作成するために使用できるコンストラクターであり、保留中、保留中、保留中の状態の 3 つの状態があります。満たされているおよび拒否されているため、これら 3 つの状態は外界の影響を受けません。状態は保留中から完了 (成功)、保留中から拒否 (失敗) までのみ変更でき、一度変更されると再度変更されることはありません。成功した結果または失敗の理由を返します。resolve、reject、catch、finally、then、all、race、done をスローします。最新の提案では、成功または失敗に関係なく返す allSettled メソッドが追加されています。次に、Promise 全体を自分で実装します

executor 関数

Promise インスタンスが作成されると、executor 関数がすぐに実行されることがわかります。 2 つのパラメータ、resolve と拒否を渡します。executor 関数が正しく実行されない場合、Promise インスタンスのステータスは拒否に変わります

class MyPromise{
    constructor(executor) {
        this.status = "pending";     // 初始化状态为pending
        this.value = undefined;      // 初始化返回的成功的结果或者失败的原因
        
        // 这里是resolve方法,成功后执行,将状态改变为resolved,并且将结果返回
        let resolve = result => {
            if(this.status !== "pending") return;  // 状态一旦改变,就不会再变
            this.status = "resolved";
            this.value = result;
        }
        
        // 这里是reject方法,异常时执行,状态改为rejected,并且将失败的原因返回
        let reject = reason => {
            if(this.status !== "pending") return;
            this.status = "rejected";
            this.value = reason;
        }
        // try、catch捕获异常,如果错误,执行reject方法
        try {
            executor(resolve, reject)
        } catch(err) {
            reject(err)
        }
    }
}
ログイン後にコピー

現在の Promise がどのようになっているかを確認してみましょう

let p1 = new MyPromise((resolve, reject) => {
    resolve(1);
})
let p2 = new MyPromise((resolve, reject) => {
    reject(2);
})
console.log(p1);
console.log(p2);
ログイン後にコピー

次のことがわかります。ステータスが変更されました。内部の の値は、成功の結果と失敗の原因でもあります。 then メソッドには 2 つのパラメータがあり、最初のパラメータは成功時に実行され、2 番目のパラメータは失敗後に実行されます。then のチェーン呼び出しは配列などと同じで、実行のたびに Promise インスタンスが返されます。成功後、最初の then で成功した関数が null の場合、null ではない関数が実行されるまで下方向の検索が続行されます。前の then で返された結果は、次の then が成功するか失敗するかに直接影響します。関数、これらを理解したら、実装してみましょう~

次にメソッド

then(resolveFn, rejectFn) {
    // 如果传入的两个参数不是函数,则直接执行返回结果
    let resolveArr = [];
    let rejectArr = [];
    
    if(typeof resolveFn !== "function") {
        resolveFn = result => {
            return result;
        }
    }
    
    if(typeof rejectFn !== "function") {
        rejectFn = reason => {
            return MyPromise.reject(reason);
        }
    }
    
    return new Mypromise((resolve, reject) => {
        resolveArr.push(result => {
            try {
                let x = resolveFn(result);
                
                if(x instanceof MyPromise) {
                    x.then(resolve, reject)
                    return;
                }
                
                resolve(x);
            } catch(err) {
                reject(err)
            }
        })
        
        rejectArr.push(reason => {
            try {
                let x = rejectFn(reason);
                
                if(x instanceof MyPromise) {
                    x.then(resolve, reject)
                    return;
                }
                
                resolve(x);
            } catch(err) {
                reject(err)
            }
        })
    })
}
ログイン後にコピー

上記のコードを整理しましょう

class MyPromise{
    constructor(executor) {
        this.status = "pending";     // 初始化状态为pending
        this.value = undefined;      // 初始化返回的成功的结果或者失败的原因
        this.resolveArr = [];        // 初始化then中成功的方法
        this.rejectArr = [];         // 初始化then中失败的方法
        
        
        // 定义change方法,因为我们发现好像resolve和reject方法共同的地方还挺多
        let change = (status, value) => {
            if(this.status !== "pending") return;  // 状态一旦改变,就不会再变
            this.status = status;
            this.value = value;
            
            // 根据状态判断要执行成功的方法或失败的方法
            let fnArr = status === "resolved" ? this.resolveArr : this.rejectArr;
            
            // fnArr中的方法依次执行
            fnArr.forEach(item => {
                if(typeof item !== "function") return;
                item(this. value);
            })
        }
        // 这里是resolve方法,成功后执行,将状态改变为resolved,并且将结果返回
        let resolve = result => {
            change("resolved", result)
        }
        
        // 这里是reject方法,异常时执行,状态改为rejected,并且将失败的原因返回
        let reject = reason => {
            change("rejected", reason);
        }
        
        // try、catch捕获异常,如果错误,执行reject方法
        try {
            executor(resolve, reject)
        } catch(err) {
            reject(err)
        }
    }
    
    then(resolveFn, rejectFn) {
    // 如果传入的两个参数不是函数,则直接执行返回结果
    
        if(typeof resolveFn !== "function") {
            resolveFn = result => {
                return result;
            }
        }
        
        if(typeof rejectFn !== "function") {
            rejectFn = reason => {
                return MyPromise.reject(reason);
            }
        }
        
        return new MyPromise((resolve, reject) => {
            this.resolveArr.push(result => {
                try {
                    let x = resolveFn(result);  // 获取执行成功方法返回的结果
                    
                    // 如果x是一个promise实例,则继续调用then方法 ==> then链的实现
                    if(x instanceof MyPromise) {
                        x.then(resolve, reject)
                        return;
                    }
                    
                    // 不是promise实例,直接执行成功的方法
                    resolve(x);
                } catch(err) {
                    reject(err)
                }
            })
            
            this.rejectArr.push(reason => {
                try {
                    let x = rejectFn(reason);
                    
                    if(x instanceof MyPromise) {
                        x.then(resolve, reject)
                        return;
                    }
                    
                    resolve(x);
                } catch(err) {
                    reject(err)
                }
            })
        })
    }
}
ログイン後にコピー

効果を見てみましょう

new MyPromise((resolve, reject) => {
    resolve(1);
}).then(res => {
    console.log(res, 'success');
}, err => {
    console.log(err, 'error');
})
ログイン後にコピー

この時点で問題が発生しました。何も出力されていないように見えます。上記のテスト例を少し変更するとどうなるでしょうか?

new MyPromise((resolve, reject) => {
    setTimeout(_ => {
        resolve(1);
    }, 0)
}).then(res => {
    console.log(res, 'success');    // 1 "success"
}, err => {
    console.log(err, 'error');
})
ログイン後にコピー

これは、Promise インスタンスの作成直後に executor 関数が実行され、then メソッドがまだ実行されていないため、成功または失敗に関係なく配列が空になるためです。それでは、setTimeout を追加するとなぜ正常に動作するのか、という疑問がまた湧くかもしれません。これは、イベントキューの仕組み上、setTimeoutがイベントキューに入れられ、メインスレッドが完了した後に実行されるためですが、このときthenメソッドには成功か失敗した関数が格納されるので、成功したかどうかは配列になります。または失敗した配列、すでに値が存在するので、この時点で実行すれば完了です~

しかし、それを使用するときにソリューションとして setTimeout を書くことはできません。カプセル化しているので、解決する必要がありますカプセル化された関数内の問題. このアイデアによると、解決メソッドと拒否メソッドが実行されるときに配列に値があるかどうかも判断できます。そうでない場合は、setTimeout を使用してその実行を遅らせることができます。コードは次のとおりですfollow~

// 这里是resolve方法,成功后执行,将状态改变为resolved,并且将结果返回
let resolve = result => {   
    // 如果数组中有值,则立即改变状态
    if(this.resolveArr.length > 0) {
        change("resolved", result)
    }
    // 如果没值,则延后改变状态
    let timer = setTimeout(_ => {
        change("resolved", result)
        clearTimeout(timer);
    }, 0)
}
// 这里是reject方法,异常时执行,状态改为rejected,并且将失败的原因返回
let reject = reason => {
// 如果数组中有值,则立即改变状态
    if(this.rejectArr.length > 0) {
        change("rejected", reason);
    }
    // 如果没值,则延后改变状态
    let timer = setTimeout(_ => {
        change("rejected", reason);
        clearTimeout(timer);
    }, 0)
}
ログイン後にコピー

さあ、試してみましょう

// 1、已经成功了
new MyPromise((resolve, reject) => {
    resolve('我成功啦,吼吼吼~~~~');            
    reject('我都已经成功了,你别想让我失败,哼~~');
}).then(res => {
    console.log(res, 'success');         // 我成功啦,吼吼吼~~~~ success
}, err => {
    console.log(err, 'error');
})
// 2、先失败了
new MyPromise((resolve, reject) => {
    reject('失败了,我好委屈,呜呜呜~~');
    resolve('已经失败了~~~');            
}).then(res => {
    console.log(res, 'success');         
}, err => {
    console.log(err, 'error');          // 失败了,我好委屈,呜呜呜~~ error
})
// 3、链式调用
new MyPromise((resolve, reject) => {
    reject('失败了,我好委屈,呜呜呜~~');
    resolve('已经失败了~~~');            
}).then(res => {
    console.log(res);
}, err => {
    console.log(err, 'error');          // 失败了,我好委屈,呜呜呜~~ error
    return '我要发奋图强,不会被困难所击倒,我要成功!!!'
}).then(res1 => {
    console.log(res1, '经过不懈努力,我终于在第二次成功了~');  // 我要发奋图强,不会被困难所击倒,我要成功!!!  经过不懈努力,我终于在第二次成功了~
}, err1 => {
    console.log(err1, '第二次失败');
})
ログイン後にコピー

これにより、最初の呼び出し時に then メソッドが実行されないという問題が完全に解決されます。同時に、連鎖呼び出しが実装されます。呼び出しの連鎖についてもう少し言いますが、実は、配列の呼び出しの連鎖とは関係なく、このインスタンスは前回返されたものであるためです。

#catch メソッド

catch メソッドは例外をキャッチするもので、then メソッドの 2 番目のコールバック関数と同じです

catch(rejectFn) {
    return this.then(null, rejectFn)
}
ログイン後にコピー

resolve メソッド

Promsie はこのようにも使用できることはわかっています

let p1 = MyPromise.resolve(1);
console.log(p1);
ログイン後にコピー

このような記述方法を期待していますが、今度は間違いなくエラーがスローされます: MyPromise.resolve isメソッドではありません

ここで、resolve メソッドをカプセル化する必要があります。明確にする必要があるのは、解決後、Promise はチェーン内での呼び出しの継続をサポートしているということです。したがって、resolve メソッドを実行して返す必要があります。 Promise インスタンス

static resolve(result) {
    // 返回新的promise实例,执行promise实例中resolve方法
    return new MyPromise(resolve => {
        resolve(result)
    })
}
ログイン後にコピー

reject メソッド

resolve メソッドと似ていますが、失敗した関数を受け取る点が異なります

static reject(reason) {
    // 返回新的promise实例,执行promise实例中reject方法
    return new MyPromise((_, reject) => {
        reject(reason);
    })
}
ログイン後にコピー

done メソッド

ES6 標準の入門書では、done メソッドの説明は次のようになります。 Promise オブジェクトのコールバック チェーンが then メソッドまたは catch メソッドで終了しても、最後のメソッドがスローする限り、エラーの場合は捕捉できない可能性があります。この目的のために、Promise は、常にコールバック チェーンの最後にあり、発生する可能性のあるエラーをスローすることが保証されている Done メソッドを提供します。さて、このメソッドが何をするかはわかったので、今から書き始めましょう ~

done(resolveFn, rejectFn) {
    this.then(resolveFn, rejectFn)
        .catch(reason => {
            setTimeout(() => {
                throw reason;
            }, 0)
        })
}
ログイン後にコピー

このメソッドは、満たされた状態と拒否された状態のコールバック関数を受け取ることができます。あるいは、パラメーターを提供することもできません。しかし、何があっても、done メソッドは考えられるエラーをすべてキャッチし、グローバル

finally メソッド

finally メソッドは成功または失敗に関係なく実行されます。 . メソッド、このようなメソッド、小さなプログラムの完全なメソッドなど、実装してみましょう~

finally(finallyFn) {
    let P = this.constructor;
    return this.then(
        value => P.resolve(finallyFn()).then(() => value),
        reason => P.reject(finallyFn()).then(() => reason)
    )
}
ログイン後にコピー

検証してみましょう

new MyPromise((resolve, reject) => {
    reject('失败了,我好委屈,呜呜呜~~');
    resolve('已经失败了~~~');
}).then(res => {
    console.log(res);
}, err => {
    console.log(err, 'error');          // 失败了,我好委屈,呜呜呜~~ error
    return '我要发奋图强,不会被困难所击倒,我要成功!!!'
}).finally(() => {
    console.log('执行了吗');            // 这里会输出"执行了吗"
})
ログイン後にコピー

すべてのメソッド#all メソッドは配列を受け取り、配列内の各インスタンスが成功すると戻ります。また、配列も返します。各パラメーターは、対応する Promise によって返される結果です。1 つの項目が失敗すると、all メソッドは失敗を返します

// 接收数组参数
static all(promiseList) {
    // 返回新实例,调用后还可使用then、catch等方法
    return new MyPromise((resolve, reject) => {
        let index = 0,      // 成功次数计数
            results = [];   // 返回的结果
        
        for(let i = 0; i < promiseList.length; i++) {
            let item = promiseList[i];
            
            // 如果item不是promise实例
            if(!(item instanceof MyPromise)) return;
            
            item.then(result => {
                index++;
                results[i] = result;
                if(index === promiseList.length) {
                    resolve(results);
                }
            }).catch(reason => {
                reject(reason);
            })
        }
    })
}
ログイン後にコピー

検証してみましょう

// 1.有失败的情况
let p1 = MyPromise.resolve(1);
let p2 = MyPromise.reject(2);
let p3 = MyPromise.resolve(3);
MyPromise.all([p1, p2, p3])
    .then(res => {
        console.log(res);
    }).catch(err => {
        console.log(err, &#39;err&#39;);     // 2 "err"
    })
// 2.无失败的情况
let p1 = MyPromise.resolve(1);
let p2 = MyPromise.resolve(2);
let p3 = MyPromise.resolve(3);
MyPromise.all([p1, p2, p3])
    .then(res => {
        console.log(res, &#39;success&#39;);   // [1, 2, 3] "success"
    }).catch(err => {
        console.log(err, &#39;err&#39;);
    })
ログイン後にコピー

レース方法

race方法同样接收一个数组参数,里面每一项是Promise实例,它返回最快改变状态的Promise实例方法的结果

static race(promiseList) {
    return new MyPromise((resolve, reject) => {
        promiseList.forEach(item => {
            if(!(item instanceof MyPromise)) return;
            
            item.then(result => {
                resolve(result);
            }).catch(err => {
                reject(err)
            })
        })
    })
}
复制代码验证
// 1.
let p1 = MyPromise.resolve(1);
let p2 = MyPromise.reject(2);
let p3 = MyPromise.resolve(3);
MyPromise.race([p1, p2, p3])
    .then(res => {
        console.log(res);            // 1 &#39;success&#39;
    }).catch(err => {
        console.log(err, &#39;err&#39;);    
    })
// 2.
let p1 = MyPromise.reject(1);
let p2 = MyPromise.resolve(2);
let p3 = MyPromise.resolve(3);
MyPromise.race([p1, p2, p3])
    .then(res => {
        console.log(res, &#39;success&#39;);   
    }).catch(err => {
        console.log(err, &#39;err&#39;);       // 1 &#39;err&#39;
    })
    
// 3.
let p1 = MyPromise.reject(1);
let p2 = MyPromise.reject(2);
let p3 = MyPromise.reject(3);
MyPromise.race([p1, p2, p3])
    .then(res => {
        console.log(res, &#39;success&#39;);   
    }).catch(err => {
        console.log(err, &#39;err&#39;);       // 1 &#39;err&#39;
    })
ログイン後にコピー

尝试实现allSettled方法

allSettled方法也是接收数组参数,但是它无论成功或者失败,都会返回

static allSettled(promiseList) {
    return new MyPromise((resolve, reject) => {
        let results = [];
        
        for(let i = 0; i < promiseList.length; i++) {
            let item = promiseList[i];
            
            if(!(item instanceof MyPromise)) return;
            
            item.then(result => {
                results[i] = result;
            }, reason => {
                results[i] = reason;
            })
            resolve(results);
        }
    })
}
复制代码验证
// 1.
let p1 = MyPromise.resolve(1);
let p2 = MyPromise.resolve(2);
let p3 = MyPromise.resolve(3);
MyPromise.race([p1, p2, p3])
    .then(res => {
        console.log(res);            // [1, 2, 3] &#39;success&#39;
    }).catch(err => {
        console.log(err, &#39;err&#39;);    
    })
// 2.
let p1 = MyPromise.reject(1);
let p2 = MyPromise.reject(2);
let p3 = MyPromise.reject(3);
MyPromise.race([p1, p2, p3])
    .then(res => {
        console.log(res, &#39;success&#39;);   // [1, 2, 3] &#39;success&#39;
    }).catch(err => {
        console.log(err, &#39;err&#39;);       
    })
    
// 3.
let p1 = MyPromise.resolve(1);
let p2 = MyPromise.reject(2);
let p3 = MyPromise.resolve(3);
MyPromise.race([p1, p2, p3])
    .then(res => {
        console.log(res, &#39;success&#39;);   // [1, 2, 3] &#39;success&#39;
    }).catch(err => {
        console.log(err, &#39;err&#39;);       
    })
ログイン後にコピー

推荐教程:《JS教程

以上がPromiseを実装するための手書きJSの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:juejin.im
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート