手書きPromise/A+仕様

105518 ワード

ソース:https://github.com/dream2023/blog/tree/master/promise
Es 6の知識:http://es6.ruanyifeng.com
この問題:https://juejin.im/post/59bfe84351882531b730bac2
Promise基礎教育:https://www.imooc.com/learn/949
Promise/A+仕様原文:https://promisesaplus.com/
Promise/A+仕様訳文:http://www.ituring.com.cn/article/66566
参考記事BATフロントエンドクラシック面接質問:史上最も詳細な手書きPromiseチュートリアル:https://juejin.im/post/5b2f02cd5188252b937548ab
参考記事手書きでPromise/A+仕様を満たすPromiseを実現する:https://www.jianshu.com/p/8d5c3a9e6181
vscodeエディタ:https://code.visualstudio.com/
vscodeコード実行プラグインcode-runner:https://marketplace.visualstudio.com/items?itemName=formulahendry.code-runner
非同期を解決するいくつかの方法
  • callback(コールバック関数)
  • generator+coライブラリ
  • Promise
  • async + await

  • 次はPromiseを手書きで書きます
    手写Promise/A+规范_第1张图片
    BATフロントエンドクラシック面接問題:史上最も詳細な手書きPromiseチュートリアル
    私たちの仕事ではpromiseを用いて非同期コールバック問題を解決することは避けられない.普段使っている多くのライブラリやプラグインにはaxios、fetchなどのpromiseが使われています.でもpromiseはどうやって書いたか知っていますか?
    恐れないで~ここにpromisesa+仕様があって、10元安く売ってあげました.
    手写Promise/A+规范_第2张图片
    1、Promiseの声明
    まずpromiseはクラスに違いありません.classで宣言します.
  • new Promise((resolve, reject)=>{})のため、パラメータ(関数)が渡され、秘籍ではexecutorと呼ばれ、渡されて実行されます.
  • executorにはresolve(成功)、reject(失敗)という2つのパラメータがあります.
  • resolveとrejectは実行可能なので、関数です.letで宣言します.
  •     class Promise{
          //    
          constructor(executor){
            //   
            let resolve = () => { };
            //   
            let reject = () => { };
            //     
            executor(resolve, reject);
          }
        }
    

    基本状態の解決
    秘籍はPromiseに規定されています.
  • Promiseには3つの状態(state)pending、fulfilled、rejected
  • が存在する.
  • pending(待機状態)は初期状態であり、fulfilled(成功状態)およびrejected(失敗状態)
  • に変換することができる.
  • が成功した場合、他の状態に移行することはできず、変更不可能な値(value)
  • が必要である.
  • が失敗した場合、他の状態に移行することはできず、変更できない原因(reason)
  • が必要である.
  • new Promise((resolve, reject)=>{resolve(value)}) resolveは成功し、パラメータvalueを受信し、状態をfulfilledに変更し、再び変更することはできません.
  • new Promise((resolve, reject)=>{reject(reason)}) rejectが失敗し、受信パラメータreason、状態がrejectedに変更され、再変更できません.
  • executor関数エラーが発生した場合、reject()を直接実行します.

  • そこで、以下のコードを入手しました.
        class Promise{
          constructor(executor){
            //    state    
            this.state = 'pending';
            //     
            this.value = undefined;
            //      
            this.reason = undefined;
            let resolve = value => {
              // state  ,resolve      
              if (this.state === 'pending') {
                // resolve   ,state      
                this.state = 'fulfilled';
                //       
                this.value = value;
              }
            };
            let reject = reason => {
              // state  ,reject      
              if (this.state === 'pending') {
                // reject   ,state      
                this.state = 'rejected';
                //        
                this.reason = reason;
              }
            };
            //   executor    ,    reject
            try{
              executor(resolve, reject);
            } catch (err) {
              reject(err);
            }
          }
        }
    

    thenメソッド
    秘籍規定:Promiseにはthenという方法があり、中には2つのパラメータがあります:onFulfilled,onRejected,成功には成功の値があり、失敗には失敗の原因があります
  • ステータスstateがfulfilledの場合、onFulfilledが実行され、this.valueに渡されます.ステータスstateがrejectedの場合、onRejectedが実行され、this.reason
  • に転送されます.
  • onFulfilled,onRejected彼らが関数である場合、fulfilled,rejectedの後に呼び出される必要があります.valueまたはreasonは、彼らの最初のパラメータ
  • として順次使用されます.
        class Promise{
          constructor(executor){...}
          // then         onFulfilled onRejected
          then(onFulfilled,onRejected) {
            //    fulfilled,  onFulfilled,      
            if (this.state === 'fulfilled') {
              onFulfilled(this.value);
            };
            //    rejected,  onRejected,       
            if (this.state === 'rejected') {
              onRejected(this.reason);
            };
          }
        }
    

    これで武学が完成し、江湖の雑毛に対処できるようになったが、settimeout付きの江洋大盗には仕方がない.
    手写Promise/A+规范_第3张图片
    非同期実装の解決
    簡単な同期コードは基本的に実現できますが、resolveがsetTomeout内で実行され、thenのstateまたはpending待機状態が必要です.then呼び出し時に、成功と失敗をそれぞれの配列に保存し、rejectまたはresolveを呼び出すと、それらを呼び出す必要があります.
    サブスクリプションのパブリッシュと同様に、then内の2つの関数を先に格納します.1つのpromiseに複数のthenがあるため、同じ配列内に存在します.
        //   then   
        let p = new Promise();
        p.then();
        p.then();
    

    成功または失敗した場合、forEachはそれらを呼び出します.
        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;
                //   resolve  ,         
                this.onResolvedCallbacks.forEach(fn=>fn());
              }
            };
            let reject = reason => {
              if (this.state === 'pending') {
                this.state = 'rejected';
                this.reason = reason;
                //   reject  ,         
                this.onRejectedCallbacks.forEach(fn=>fn());
              }
            };
            try{
              executor(resolve, reject);
            } catch (err) {
              reject(err);
            }
          }
          then(onFulfilled,onRejected) {
            if (this.state === 'fulfilled') {
              onFulfilled(this.value);
            };
            if (this.state === 'rejected') {
              onRejected(this.reason);
            };
            //    state pending 
            if (this.state === 'pending') {
              // onFulfilled       
              this.onResolvedCallbacks.push(()=>{
                onFulfilled(this.value);
              })
              // onRejected       
              this.onRejectedCallbacks.push(()=>{
                onRejected(this.reason);
              })
            }
          }
        }
    

    チェーンコールの解決
    私のドアはよくnew Promise().then().then()に使われています.これがチェーン呼び出しで、コールバック地獄を解決します.
    Promise中継状態は変更できないため、チェーン呼び出しを実現するにはthenメソッドの実行後の戻り値が新しいインスタンスである必要があります.
    promiseのチェーン呼び出し(2つのケースに分けて議論)
    promiseのthenメソッドはその後もpromiseオブジェクトを返します
    例は次のとおりです.
    let test = new Promise((resolve, reject) => {
        let random = Math.random()
        if (random > 0.5) {
            resolve('  0.5')
        } else {
            reject('    0.5')
        }
    })
    
    let p = test.then((result) => {
        console.log(result)
        return result
    }).catch((result) => {
        console.log(result)
        return result
    }).then((result) => {
        console.log(result)
        return result
    }).then((result) => {
        console.log('last', result)
    })
    
    console.log(p)
    

    nodeを使用して実行した結果は次のとおりです.
      p:  Promise { <pending> }
      0.5
      0.5
    last   0.5
    

    結果から、thenメソッドを使用するとpromiseオブジェクトが返され、thenメソッドで呼び出し続けることができ、取得したパラメータが前のthenメソッドreturnの内容であることがわかります.
    上記の例は、1回目のthen以降に返されるpromiseではrejectの状態は現れないが、1回目のthen以降にrejectの状態を含む可能性のあるpromiseを手動で返すと、エラーチェーン呼び出し中のエラー処理が大きな問題になる.2回目のthenはエラー処理メカニズムを持たないため、これはpromiseのreject状態に対応するエラー処理メカニズムを直接書くことを要求する.
    Promiseのresove,reject,all,raceメソッド実装
    //resolve  
    Promise.resolve = function(val){
      return new Promise((resolve,reject)=>{
        resolve(val)
      });
    }
    //reject  
    Promise.reject = function(val){
      return new Promise((resolve,reject)=>{
        reject(val)
      });
    }
    //race   
    Promise.race = function(promises){
      return new Promise((resolve,reject)=>{
        for(let i=0;i<promises.length;i++){
          promises[i].then(resolve,reject)
        };
      })
    }
    //all  (     promise,   then,       ,    )
    Promise.all = function(promises){
      let arr = [];
      let i = 0;
      function processData(index,data){
        arr[index] = data;
        i++;
        if(i == promises.length){
          resolve(arr);
        };
      };
      return new Promise((resolve,reject)=>{
        for(let i=0;i<promises.length;i++){
          promises[i].then(data=>{
            processData(i,data);
          },reject);
        };
      });
    }
    
    

    後ろの部分はまだはっきり理解していません(続き..)
    1、チェーン式を達成するために、最初のthenでpromiseをデフォルトで返します.秘籍はthenの中で新しいpromiseを返す方法を規定しています.promise 2:promise2 = new Promise((resolve, reject)=>{})と呼ばれています.
  • このpromise 2が返す値を次のthenの
  • に渡す
  • 通常の値を返すと、次のthenの
  • に通常の値が渡される.
    2、私たちが最初のthenの中でreturnのパラメータ(パラメータが未知で、判断する必要があります).このreturnから出てきた新しいpromiseはonFulfilled()またはonRejected()の値です
    秘籍ではonFulfilled()またはonRejected()の値、すなわち最初のthenが返す値をxと規定し、xを判断する関数をresolvePromiseと呼ぶ
  • まず、xがpromiseかどうかを見ます.
  • promiseであれば、その結果を取って、新しいpromise 2が成功した結果として
  • 通常値であればpromise 2として直接成功した結果
  • .
  • だからxとpromise 2
  • を比較する
  • resolvePromiseのパラメータにはpromise 2(デフォルトで返されるpromise)、x(私たち自身returnのオブジェクト)、resolve、reject
  • があります.
  • resolveとrejectはpromise 2の
  • です
        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) {
            //      promise2
            let promise2 = new Promise((resolve, reject)=>{
              if (this.state === 'fulfilled') {
                let x = onFulfilled(this.value);
                // resolvePromise  ,    return promise    promise2   
                resolvePromise(promise2, x, resolve, reject);
              };
              if (this.state === 'rejected') {
                let x = onRejected(this.reason);
                resolvePromise(promise2, x, resolve, reject);
              };
              if (this.state === 'pending') {
                this.onResolvedCallbacks.push(()=>{
                  let x = onFulfilled(this.value);
                  resolvePromise(promise2, x, resolve, reject);
                })
                this.onRejectedCallbacks.push(()=>{
                  let x = onRejected(this.reason);
                  resolvePromise(promise2, x, resolve, reject);
                })
              }
            });
            //   promise,    
            return promise2;
          }
        }
    

    resolvePromise関数の完了
    秘籍はコードを規定して、異なるpromiseコードを互いに組み合わせて、resolvePromiseと言います
  • x==promise 2の場合、ループ参照が発生し、自分で完了するのを待つ場合、「ループ参照」エラー
  • が報告されます.
        let p = new Promise(resolve => {
          resolve(0);
        });
        var p2 = p.then(data => {
          //     ,        ,      
          return p2;
        })
    

    1、判断x
  • Otherwise, if x is an object or function,Let then be x.then
  • xはnull
  • ではありません
  • xは通常値直接resolve(x)
  • xはオブジェクトまたは関数(promiseを含む)であり、let then = x.then xはオブジェクトまたは関数(デフォルトpromise)
  • である.
  • はthen
  • を宣言した
  • thenを取り間違えたらreject()
  • を歩く
  • thenが関数である場合、callでthenが実行され、最初のパラメータはthisであり、その後は成功したコールバックと失敗したコールバック
  • である.
  • 正常なコールバックがpormiseである場合、再帰的に解析を継続する3、成功と失敗は1つしか呼び出せないので、
  • の複数回の呼び出しを防止するためにcalledを設定する.
        function resolvePromise(promise2, x, resolve, reject){
          //       
          if(x === promise2){
            // reject  
            return reject(new TypeError('Chaining cycle detected for promise'));
          }
          //       
          let called;
          // x  null  x       
          if (x != null && (typeof x === 'object' || typeof x === 'function')) {
            try {
              // A+  ,  then = x then  
              let then = x.then;
              //   then   ,    promise 
              if (typeof then === 'function') { 
                //   then         this                   
                then.call(x, y => {
                  //            
                  if (called) return;
                  called = true;
                  // resolve      promise       
                  resolvePromise(promise2, y, resolve, reject);
                }, err => {
                  //            
                  if (called) return;
                  called = true;
                  reject(err);//        
                })
              } else {
                resolve(x); //       
              }
            } catch (e) {
              //      
              if (called) return;
              called = true;
              //  then             
              reject(e); 
            }
          } else {
            resolve(x);
          }
        }
    

    その他の問題の解決
    1、秘籍規定onFulfilled、onRejectedはすべてオプションのパラメータで、もし彼らが関数でなければ、無視しなければならない.
  • onFulfilledは通常の値を返し、成功した場合はvalue => value
  • に直接等しい.
  • onRejectedは通常の値を返します.失敗した場合、value=>valueに直接等しいと、次のthenのonFulfilledに駆け込むので、エラーreason => throw err、秘籍規定onFulfilledまたはonRejectedは同期して呼び出されず、非同期で呼び出さなければなりません.settimeoutを用いて非同期問題
  • を解決する
  • onFulfilledまたはonRejectedが間違っている場合は、reject()
  • に直接戻ります.
        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      ,   onFulfilled,    value
            onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
            // onRejected      ,   onRejected,      
            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)
                });
              };
            });
            //   promise,    
            return promise2;
          }
        }
    

    大功を成し遂げる
    ついでにcatchとresolve、reject、race、allメソッドを添付します
        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;
          }
          catch(fn){
            return this.then(null,fn);
          }
        }
        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);
          }
        }
        //resolve  
        Promise.resolve = function(val){
          return new Promise((resolve,reject)=>{
            resolve(val)
          });
        }
        //reject  
        Promise.reject = function(val){
          return new Promise((resolve,reject)=>{
            reject(val)
          });
        }
        //race   
        Promise.race = function(promises){
          return new Promise((resolve,reject)=>{
            for(let i=0;i<promises.length;i++){
              promises[i].then(resolve,reject)
            };
          })
        }
        //all  (     promise,   then,       ,    )
        Promise.all = function(promises){
          let arr = [];
          let i = 0;
          function processData(index,data){
            arr[index] = data;
            i++;
            if(i == promises.length){
              resolve(arr);
            };
          };
          return new Promise((resolve,reject)=>{
            for(let i=0;i<promises.length;i++){
              promises[i].then(data=>{
                processData(i,data);
              },reject);
            };
          });
        }
    

    私たちのpromiseが正しいかどうかを検証する方法
    1、先に下記のコードを付けます
    2、npmにはpromises-aplus-testsプラグインnpm i promises-aplus-tests-gがあり、macユーザーの最前線にsudoを加えることができる.
    3、コマンドラインpromises-aplus-tests[jsファイル名]で検証できる
        //                  
        //    
        Promise.defer = Promise.deferred = function () {
          let dfd = {}
          dfd.promise = new Promise((resolve,reject)=>{
            dfd.resolve = resolve;
            dfd.reject = reject;
          });
          return dfd;
        }
        module.exports = Promise;
        //npm install promises-aplus-tests        promise     promisesA+