完全な Promise を実装する方法

一个新手
リリース: 2018-05-15 17:34:06
オリジナル
2423 人が閲覧しました

Promiseを使ったことがあるのですが、いつもよくわからないと感じました。たくさんの記事を読んだのですが、それでもPromiseの実装原理が理解できませんでした。その後、記事を読んでコードをデバッグしました。徐々に感覚をつかんできたのですが、以下は自分の理解に従ってPromiseを実装するだけです。

コードを完全に理解するには、この とクロージャ の意味を理解する必要があります。

Promise とは

簡単に言えば、Promise は主に非同期コールバックの問題を解決することを目的としています。 Promise を使用して非同期コールバックを処理すると、コード階層が明確になり、理解しやすく、保守しやすくなります。現在の主流の仕様は主に 約束/A+。 Promise の使い方がよくわからない場合は、私の記事「es6 勉強記 5 - promise」を参照して理解できたら、この記事を読んでください。

始める前に、Promise の役割を体験するために、Promise アプリケーションのシナリオを書いてみましょう。 Google と Firefox は現在、es6 Promise をサポートしています。 setTimeout を使用して非同期操作をシミュレートします。具体的なコードは次のとおりです。

function fn1(resolve, reject) {
    setTimeout(function() {
        console.log('步骤一:执行');
        resolve('1');
    },500);
}

function fn2(resolve, reject) {
    setTimeout(function() {
        console.log('步骤二:执行');
        resolve('2');
    },100);
}

new Promise(fn1).then(function(val){
    console.log(val);
    return new Promise(fn2);
}).then(function(val){
    console.log(val);
    return 33;
}).then(function(val){
    console.log(val);
});
ログイン後にコピー

最終的に、私たちが書いた Promise もこの機能を実現できます。

初期構築

簡単なpromsieを書いてみましょう。 Promiseの引数は関数fnで、内部で定義したresolveメソッドを引数としてfnに渡してfnを呼び出します。非同期操作が成功すると、resolveメソッドが呼び出され、そこに登録されているコールバック関数が実行されます。

function Promise(fn){
  //需要一个成功时的回调
  var callback;
  //一个实例的方法,用来注册异步事件
  this.then = function(done){
    callback = done;
  }
  function resolve(){
    callback();
  }
  fn(resolve);
}
ログイン後にコピー

チェーンのサポートを追加します

以下にチェーンを追加します。成功したコールバック メソッドは、保存する前に配列に変換する必要があります。同時に、resolveメソッドにパラメータを追加し、未定義が出力されないようにします。

function Promise(fn) {
    var promise = this,
        value = null;
        promise._resolves = [];

    this.then = function (onFulfilled) {
        promise._resolves.push(onFulfilled);
        return this;
    };

    function resolve(value) {
        promise._resolves.forEach(function (callback) {
            callback(value);
        });
    }

    fn(resolve);
}
ログイン後にコピー
  • promise = this なので、ある時点でこのポインタが突然変化することを心配する必要はありません。

  • then メソッドを呼び出し、promise._resolvesキューにコールバックを入れます。

  • 非同期操作のときに、Promise オブジェクトを作成し、その fn を呼び出し、resolve メソッド を渡します。 fn が正常に実行されました。その後、resolve が呼び出されます。つまり、<code><span style="color:#000000">promise._resolves</span>列中的回调;

  • <span style="color:#000000">resolve 方法</span>接收一个参数,即异步操作返回的结果,方便传值。

  • then方法中的 return this 实现了链式调用。

但是,目前的 Promise 还存在一些问题,如果我传入的是一个不包含异步操作的函数,resolve就会先于 then 执行,也就是说 promise._resolves 是一个空数组。

为了解决这个问题,我们可以在 resolve 中添加 setTimeout,来将 resolvepromise._resolves チームの

列のコールバックが実行されます。

</p>resolve メソッド<p> は、非同期操作によって返された結果であるパラメーター

を受け取るため、値を渡します。

then メソッドの return this はチェーン呼び出しを実装します。

ただし、現在の Promise にはまだ問題がいくつかあります。非同期操作を含まない関数を渡すと、その前に replace が実行されます。つまり、

promise._resolves 配列が空になります。 。

🎜🎜 この問題を解決するには、resolve に setTimeout を追加して、resolve 内のコールバック実行ロジックを JS タスク キューの最後に配置します。 🎜🎜
    function resolve(value) {
        setTimeout(function() {
            promise._resolves.forEach(function (callback) {
                callback(value);
            });
        },0);
    }
ログイン後にコピー
🎜状態の紹介🎜🎜Promiseの基本の分析 ここには問題があります。Promise の非同期操作が成功した場合、それまでに登録されたコールバックは二度と実行されません。これは私たちの期待と一致しません。 🎜🎜この文がよくわかりません。何かご存知の場合は、例を挙げてメッセージを残してください。ただし、個人的には、 then に登録されたコールバックは、resolve が実行される前に配列に追加されるため、非実行は発生しないと思います。 🎜🎜上記の手順に従ってステータスを導入します: 🎜
<p style="margin-bottom: 7px;">function Promise(fn) {<br/>    var promise = this,<br/>        value = null;<br/>        promise._resolves = [];<br/>        promise._status = &#39;PENDING&#39;;<br/><br/>    this.then = function (onFulfilled) {<br/>        if (promise._status === &#39;PENDING&#39;) {<br/>            promise._resolves.push(onFulfilled);<br/>            return this;<br/>        }<br/>        onFulfilled(value);<br/>        return this;<br/>    };<br/>    function resolve(value) {<br/>        setTimeout(function(){<br/>            promise._status = "FULFILLED";<br/>            promise._resolves.forEach(function (callback) {<br/>                callback(value);<br/>            })<br/>        },0);<br/>    }<br/><br/>    fn(resolve);<br/>}<br/></p>
ログイン後にコピー
🎜 各 Promise には、保留中、履行済み、拒否という 3 つの相互に排他的なステータスがあります。 Promise オブジェクトの状態が変化する可能性は 2 つだけあります。保留中から実行済みへ、および保留中から拒否へです。この 2 つの状況が発生する限り、状態は固定され、再び変化することはなく、この結果が維持されます。すでに変更が行われている場合でも、Promise オブジェクトにコールバック関数を追加すると、すぐに結果が得られます。イベントとは全く違いますが、聞き逃してまた聞き直すと結果が出ないのがイベントの特徴です。 🎜🎜非同期結果の配信を追加します🎜🎜現在の書き込みメソッドでは、非同期で返される結果の配信を考慮していません:🎜
    function resolve(value) {
        setTimeout(function(){
            promise._status = "FULFILLED";
            promise._resolves.forEach(function (callback) {
                value = callback(value);
            })
        },0);
    }
ログイン後にコピー
🎜Serial Promise🎜🎜 Serial Promise は、現在の Promise が達成された状態に達することを意味します。それから、次の約束(次の約束)が始まります。たとえば、最初に ajax を使用してユーザーのデータをバックグラウンドから取得し、次にこのデータに基づいて他のデータを取得します。 🎜🎜ここでは主に then メソッドを変更します: 🎜
    this.then = function (onFulfilled) {
        return new Promise(function(resolve) {
            function handle(value) {
                var ret = isFunction(onFulfilled) && onFulfilled(value) || value;
                resolve(ret);
            }
            if (promise._status === &#39;PENDING&#39;) {
                promise._resolves.push(handle);
            } else if(promise._status === FULFILLED){
                handle(value);
            }
        })
        
    };
ログイン後にコピー
🎜 then メソッドには多くの変更が必要です。ここで説明しましょう: 🎜
  • 注意的是,new Promise() 中匿名函数中的 promise (promise._resolves中的 promise)指向的都是上一个 promise 对象, 而不是当前这个刚刚创建的。

  • 首先我们返回的是新的一个promise对象,因为是同类型,所以链式仍然可以实现。

  • 其次,我们添加了一个 handle 函数,handle 函数对上一个 promise 的 then 中回调进行了处理,并且调用了当前的 promise 中的 resolve 方法。

  • 接着将 handle 函数添加到 上一个promise 的 promise._resolves 中,当异步操作成功后就会执行 handle 函数,这样就可以 执行 当前 promise 对象的回调方法。我们的目的就达到了。

有些人在这里可能会有点犯晕,有必要对执行过程分析一下,具体参看以下代码:

new Promise(fn1).then(fn2).then(fn3)})
ログイン後にコピー

fn1, fn2, fn3的函数具体可参看最前面的定义。

  1. 首先我们创建了一个 Promise 实例,这里叫做 promise1;接着会运行 fn1(resolve);

  2. 但是 fn1 中有一个 setTimeout 函数,于是就会先跳过这一部分,运行后面的第一个 then 方法;

  3. then 返回一个新的对象 promise2, promise2 对象的 resolve方法和 then 方法的中回调函数 fn2 都被封装在 handle 中, 然后 handle 被添加到   promise1._resolves 数组中。

  4. 接着运行第二个 then 方法,同样返回一个新的对象 promise3, 包含 promise3 的 resolve 方法和回调函数 fn3 的 handle 方法被添加到 promise2._resolves 数组中。

  5. 到此两个 then 运行结束。 setTimeout 中的延迟时间一到,就会调用 promise1的 resolve方法。

  6. resolve 方法的执行,会调用 promise1._resolves 数组中的回调,之前我们添加的 handle 方法就会被执行; 也就是 fn2 和 promsie2 的 resolve 方法,都被调用了。

  7. 以此类推,fn3 会和promise3 的 resolve 方法 一起执行,因为后面没有 then 方法了,promise3._resolves 数组是空的

  8. 至此所有回调执行结束

但这里还存在一个问题,就是我们的 then 里面函数不能对 Promise 对象进行处理。这里我们需要再次对 then 进行修改,使其能够处理 promise 对象。

this.then = function (onFulfilled) {
        return new Promise(function(resolve) {
            function handle(value) {
                var ret = typeof onFulfilled === &#39;function&#39; && onFulfilled(value) || value;
                if( ret && typeof ret [&#39;then&#39;] == &#39;function&#39;){
                    ret.then(function(value){
                       resolve(value);
                    });
                } else {
                    resolve(ret);
                }
            }
            if (promise._status === &#39;PENDING&#39;) {
                promise._resolves.push(handle);
            } else if(promise._status === FULFILLED){
                handle(value);
            }
        })
        
    };
ログイン後にコピー

在 then 方法里面,我们对 ret 进行了判断,如果是一个 promise 对象,就会调用其 then 方法,形成一个嵌套,直到其不是promise对象为止。同时 在 then 方法中我们添加了调用 resolve 方法,这样链式得以维持。

失败处理

异步操作不可能都成功,在异步操作失败时,标记其状态为 rejected,并执行注册的失败回调。

有了之前处理 fulfilled 状态的经验,支持错误处理变得很容易。毫无疑问的是,在注册回调、处理状态变更上都要加入新的逻辑:

    this.then = function (onFulfilled, onRejected) {
        return new Promise(function(resolve, reject) {
            function handle(value) {
                .......
            }
            function errback(reason){
                reason = isFunction(onRejected) && onRejected(reason) || reason;
                reject(reason);
            }
            if (promise._status === &#39;PENDING&#39;) {
                promise._resolves.push(handle);
                promise._rejects.push(errback);
            } else if(promise._status === &#39;FULFILLED&#39;){
                handle(value);
            } else if(promise._status === &#39;REJECTED&#39;) {
              errback(promise._reason);
        }
        })
        
    };

    function reject(value) {
        setTimeout(function(){
            promise._status = "REJECTED";
            promise._rejects.forEach(function (callback) {
                promise._reason = callback( value);
            })
        },0);
    }
ログイン後にコピー

添加Promise.all方法

Promise.all 可以接收一个元素为 Promise 对象的数组作为参数,当这个数组里面所有的 Promise 对象都变为 resolve 时,该方法才会返回。

具体代码如下:

Promise.all = function(promises){
    if (!Array.isArray(promises)) {
        throw new TypeError(&#39;You must pass an array to all.&#39;);
      }
    // 返回一个promise 实例
      return new Promise(function(resolve,reject){
          var i = 0,
              result = [],
              len = promises.length,
              count = len;

      // 每一个 promise 执行成功后,就会调用一次 resolve 函数
          function resolver(index) {
            return function(value) {
               resolveAll(index, value);
            };
         }

        function rejecter(reason){
            reject(reason);
        }

        function resolveAll(index,value){
       // 存储每一个promise的参数
            result[index] = value;
       // 等于0 表明所有的promise 都已经运行完成,执行resolve函数
            if( --count == 0){
                resolve(result)
            }
        }
      // 依次循环执行每个promise
          for (; i < len; i++) {
         // 若有一个失败,就执行rejecter函数 
              promises[i].then(resolver(i),rejecter);
          }
      });
}
ログイン後にコピー

Promise.all会返回一个 Promise 实例,该实例直到参数中的所有的 promise 都执行成功,才会执行成功回调,一个失败就会执行失败回调。

日常开发中经常会遇到这样的需求,在不同的接口请求数据然后拼合成自己所需的数据,通常这些接口之间没有关联(例如不需要前一个接口的数据作为后一个接口的参数),这个时候 Promise.all 方法就可以派上用场了。

添加Promise.race方法

该函数和 Promise.all 相类似,它同样接收一个数组,不同的是只要该数组中的任意一个 Promise 对象的状态发生变化(无论是 resolve 还是 reject)该方法都会返回。我们只需要对 Promise.all 方法稍加修改就可以了。

Promise.race = function(promises){    if (!Array.isArray(promises)) {        throw new TypeError(&#39;You must pass an array to race.&#39;);
    }    return Promise(function(resolve,reject){        var i = 0,
            len = promises.length;        function resolver(value) {
            resolve(value);
        }        function rejecter(reason){
            reject(reason);
        }        for (; i < len; i++) {
            promises[i].then(resolver,rejecter);
        }
    });
}
ログイン後にコピー

代码中没有类似一个 resolveAll 的函数,因为我们不需要等待所有的 promise 对象状态都发生变化,只要一个就可以了。

添加其他API以及封装函数

到这里,Promise 的主要API都已经完成了,另外我们在添加一些比较常见的方法。也对一些可能出现的错误进行了处理,最后对其进行封装。

完整的代码如下:

(function(window,undefined){

// resolve 和 reject 最终都会调用该函数
var final = function(status,value){
    var promise = this, fn, st;
        
    if(promise._status !== &#39;PENDING&#39;) return;
    
    // 所以的执行都是异步调用,保证then是先执行的
    setTimeout(function(){
        promise._status = status;
        st = promise._status === &#39;FULFILLED&#39;
        queue = promise[st ? &#39;_resolves&#39; : &#39;_rejects&#39;];

        while(fn = queue.shift()) {
            value = fn.call(promise, value) || value;
        }

        promise[st ? &#39;_value&#39; : &#39;_reason&#39;] = value;
        promise[&#39;_resolves&#39;] = promise[&#39;_rejects&#39;] = undefined;
    });
}


//参数是一个函数,内部提供两个函数作为该函数的参数,分别是resolve 和 reject
var Promise = function(resolver){
    if (!(typeof resolver === &#39;function&#39; ))
        throw new TypeError(&#39;You must pass a resolver function as the first argument to the promise constructor&#39;);
    //如果不是promise实例,就new一个
    if(!(this instanceof Promise)) return new Promise(resolver);

    var promise = this;
    promise._value;
    promise._reason;
    promise._status = &#39;PENDING&#39;;
    //存储状态
    promise._resolves = [];
    promise._rejects = [];
    
    //
    var resolve = function(value) {
        //由於apply參數是數組
        final.apply(promise,[&#39;FULFILLED&#39;].concat([value]));
    }

    var reject = function(reason){
        final.apply(promise,[&#39;REJECTED&#39;].concat([reason]));
    }
    
    resolver(resolve,reject);
}

Promise.prototype.then = function(onFulfilled,onRejected){
    var promise = this;
    // 每次返回一个promise,保证是可thenable的
    return new Promise(function(resolve,reject){
        
        function handle(value) {
            // 這一步很關鍵,只有這樣才可以將值傳遞給下一個resolve
            var ret = typeof onFulfilled === &#39;function&#39; && onFulfilled(value) || value;

            //判断是不是promise 对象
            if (ret && typeof ret [&#39;then&#39;] == &#39;function&#39;) {
                ret.then(function(value) {
                    resolve(value);
                }, function(reason) {
                    reject(reason);
                });
            } else {
                resolve(ret);
            }
        }

        function errback(reason){
            reason = typeof onRejected === &#39;function&#39; && onRejected(reason) || reason;
            reject(reason);
        }

        if(promise._status === &#39;PENDING&#39;){
            promise._resolves.push(handle);
            promise._rejects.push(errback);
        }else if(promise._status === FULFILLED){ // 状态改变后的then操作,立刻执行
            callback(promise._value);
        }else if(promise._status === REJECTED){
            errback(promise._reason);
        }
    });
}

Promise.prototype.catch = function(onRejected){
    return this.then(undefined, onRejected)
}

Promise.prototype.delay = function(ms,value){
    return this.then(function(ori){
        return Promise.delay(ms,value || ori);
    })
}

Promise.delay = function(ms,value){
    return new Promise(function(resolve,reject){
        setTimeout(function(){
            resolve(value);
            console.log(&#39;1&#39;);
        },ms);
    })
}

Promise.resolve = function(arg){
    return new Promise(function(resolve,reject){
        resolve(arg)
    })
}

Promise.reject = function(arg){
    return Promise(function(resolve,reject){
        reject(arg)
    })
}

Promise.all = function(promises){
    if (!Array.isArray(promises)) {
        throw new TypeError(&#39;You must pass an array to all.&#39;);
    }
    return Promise(function(resolve,reject){
        var i = 0,
            result = [],
            len = promises.length,
            count = len
            
        //这里与race中的函数相比,多了一层嵌套,要传入index
        function resolver(index) {
          return function(value) {
            resolveAll(index, value);
          };
        }

        function rejecter(reason){
            reject(reason);
        }

        function resolveAll(index,value){
            result[index] = value;
            if( --count == 0){
                resolve(result)
            }
        }

        for (; i < len; i++) {
            promises[i].then(resolver(i),rejecter);
        }
    });
}

Promise.race = function(promises){
    if (!Array.isArray(promises)) {
        throw new TypeError(&#39;You must pass an array to race.&#39;);
    }
    return Promise(function(resolve,reject){
        var i = 0,
            len = promises.length;

        function resolver(value) {
            resolve(value);
        }

        function rejecter(reason){
            reject(reason);
        }

        for (; i < len; i++) {
            promises[i].then(resolver,rejecter);
        }
    });
}

window.Promise = Promise;

})(window);
ログイン後にコピー

代码写完了,总要写几个实例看看效果啊,具体看下面的测试代码:

var getData100 = function(){
    return new Promise(function(resolve,reject){
        setTimeout(function(){
            resolve(&#39;100ms&#39;);
        },1000);
    });
}

var getData200 = function(){
    return new Promise(function(resolve,reject){
        setTimeout(function(){
            resolve(&#39;200ms&#39;);
        },2000);
    });
}
var getData300 = function(){
    return new Promise(function(resolve,reject){
        setTimeout(function(){
            reject(&#39;reject&#39;);
        },3000);
    });
}

getData100().then(function(data){
    console.log(data);      // 100ms
    return getData200();
}).then(function(data){
    console.log(data);      // 200ms
    return getData300();
}).then(function(data){
    console.log(data);      
}, function(data){
    console.log(data);      // &#39;reject&#39;
});

Promise.all([getData100(), getData200()]).then(function(data){
    console.log(data);      // [ "100ms", "200ms" ]
});

Promise.race([getData100(), getData200(), getData300()]).then(function(data){
    console.log(data);      // 100ms
});
Promise.resolve(&#39;resolve&#39;).then(function(data){
    console.log(data);      //&#39;resolve&#39;
})
Promise.reject(&#39;reject函数&#39;).then(function(data){
    console.log(data);
}, function(data){
    console.log(data);     //&#39;reject函数&#39;
})
ログイン後にコピー

以上が完全な Promise を実装する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

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