Heim > Web-Frontend > js-Tutorial > Wie Sie ein vollständiges Versprechen umsetzen

Wie Sie ein vollständiges Versprechen umsetzen

一个新手
Freigeben: 2018-05-15 17:34:06
Original
2457 Leute haben es durchsucht

Ich habe Promise verwendet, aber ich habe immer das Gefühl, dass ich es nicht verstehe. Ich habe viele Artikel gelesen, aber ich verstehe das Implementierungsprinzip von Promise immer noch nicht. Und schließlich begann ich es langsam zu verstehen: Lasst uns ein Versprechen nach unserem eigenen Verständnis umsetzen.

Um den Code vollständig zu verstehen, müssen Sie die Bedeutung dieses und des Abschlusses verstehen.

Was ist Promise?

Einfach ausgedrückt dient Promise hauptsächlich dazu, das Problem asynchroner Rückrufe zu lösen. Durch die Verwendung von Promise zur Verarbeitung asynchroner Rückrufe wird die Codehierarchie klarer, leicht verständlich und einfacher zu warten. Seine Mainstream-Spezifikationen sind derzeit hauptsächlich Versprechen/A+. Wenn Sie mit der Verwendung von Promise nicht vertraut sind, können Sie sich auf meinen Artikel beziehen – es6-Studiennotizen 5 – Promise. Wenn Sie es verstehen, lesen Sie diesen Artikel, er wird Ihnen eine große Hilfe sein.

Bevor wir beginnen, schreiben wir ein Anwendungsszenario für Versprechen, um die Rolle von Versprechen kennenzulernen. Google und Firefox unterstützen derzeit es6-Versprechen. Wir verwenden setTimeout, um den asynchronen Betrieb zu simulieren:

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);
});
Nach dem Login kopieren

Am Ende kann das Versprechen, das wir geschrieben haben, auch diese Funktion erreichen.

Erste Konstruktion

Lassen Sie uns ein einfaches Versprechen schreiben. Der Parameter von Promise ist die Funktion fn. Übergeben Sie die intern definierte Auflösungsmethode als Parameter an fn und rufen Sie fn auf. Wenn der asynchrone Vorgang erfolgreich ist, wird die Auflösungsmethode aufgerufen und anschließend die darin registrierte Rückruffunktion ausgeführt.

function Promise(fn){
  //需要一个成功时的回调
  var callback;
  //一个实例的方法,用来注册异步事件
  this.then = function(done){
    callback = done;
  }
  function resolve(){
    callback();
  }
  fn(resolve);
}
Nach dem Login kopieren

Verkettungsunterstützung hinzufügen

Fügen Sie unten die Verkettung hinzu. Die erfolgreiche Rückrufmethode muss in ein Array konvertiert werden, bevor sie gespeichert werden kann. Gleichzeitig fügen wir der Auflösungsmethode Parameter hinzu, damit undefiniert nicht ausgegeben wird.

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);
}
Nach dem Login kopieren
  • promise = this, damit wir uns keine Sorgen über die plötzliche Änderung dieses Zeigers irgendwann machen müssen.

  • Rufen Sie die then-Methode auf und stellen Sie den Rückruf in die Warteschlange promise._resolves;

  • Rufen Sie beim Erstellen des Promise-Objekts dessen fn auf und übergeben Sie die Auflösungsmethode . Wenn die asynchrone Operation von fn erfolgreich ausgeführt wird, wird „resolve“ aufgerufen, also <code><span style="color:#000000">promise._resolves</span> Promise._resolves Rückruf in der Spalte

  • <code><span style="color:#000000">resolve 方法</span>resolve method empfängt einen Parameter sind die Ergebnisse, die von asynchronen Vorgängen zurückgegeben werden, was die Übergabe von Werten erleichtert.

  • Die return this in der then-Methode implementiert Kettenaufrufe.

Es gibt jedoch immer noch einige Probleme mit dem aktuellen Promise. Wenn ich eine Funktion übergebe, die keine asynchronen Operationen enthält, wird die Auflösung vorher ausgeführt sagen wir, promise._resolves ist ein leeres Array.

resolveUm dieses Problem zu lösen, können wir „setTimeout“ in „resolution“ hinzufügen, um die Callback-Ausführungslogik in am Ende der JS-Aufgabenwarteschlange zu platzieren.

    function resolve(value) {
        setTimeout(function() {
            promise._resolves.forEach(function (callback) {
                callback(value);
            });
        },0);
    }
Nach dem Login kopieren

Einführung des Staates

Analyse der Grundlagen von Promise Hier liegt ein Problem vor: Wenn der asynchrone Promise-Vorgang erfolgreich war, wird der bis dahin registrierte Rückruf nie wieder ausgeführt, was nicht unseren Erwartungen entspricht.

Ich verstehe diesen Satz nicht ganz. Wenn Sie etwas wissen, können Sie am besten ein Beispiel hinterlassen. Aber ich persönlich denke, dass die darin registrierten Rückrufe dem Array hinzugefügt werden, bevor die Auflösung ausgeführt wird, sodass es keine Nichtausführung geben wird.

Befolgen Sie die oben genannten Schritte und führen Sie Zustände ein:
<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>
Nach dem Login kopieren

Jedes Versprechen hat drei sich gegenseitig ausschließende Zustände: ausstehend, erfüllt und abgelehnt. Es gibt nur zwei Möglichkeiten, den Status eines Promise-Objekts zu ändern: von „Ausstehend“ zu „Erfüllt“ und von „Ausstehend“ zu „Abgelehnt“. Solange diese beiden Situationen auftreten, wird der Zustand gefestigt und wird sich nicht mehr ändern und dieses Ergebnis beibehalten. Selbst wenn die Änderung bereits erfolgt ist, erhalten Sie das Ergebnis sofort, wenn Sie dem Promise-Objekt eine Rückruffunktion hinzufügen. Das ist etwas ganz anderes als bei einem Ereignis. Das Besondere an einem Ereignis ist, dass man kein Ergebnis erhält, wenn man es verpasst und noch einmal zuhört.

Übertragung asynchroner Ergebnisse hinzufügen

Die aktuelle Schreibmethode berücksichtigt nicht die Übertragung asynchron zurückgegebener Ergebnisse:
    function resolve(value) {
        setTimeout(function(){
            promise._status = "FULFILLED";
            promise._resolves.forEach(function (callback) {
                value = callback(value);
            })
        },0);
    }
Nach dem Login kopieren

Serial Promise

Serielles Versprechen bedeutet, dass, nachdem das aktuelle Versprechen den erfüllten Zustand erreicht hat, das nächste Versprechen (das nächste Versprechen) beginnt. Beispielsweise verwenden wir zunächst Ajax, um Benutzerdaten aus dem Hintergrund abzurufen, und erhalten dann andere Daten basierend auf diesen Daten.

Hier modifizieren wir hauptsächlich die Then-Methode:
    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);
            }
        })
        
    };
Nach dem Login kopieren

Die Then-Methode erfordert viele Änderungen, lassen Sie es mich hier erklären: <🎜>
  • 注意的是,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)})
Nach dem Login kopieren

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);
            }
        })
        
    };
Nach dem Login kopieren

在 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);
    }
Nach dem Login kopieren

添加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);
          }
      });
}
Nach dem Login kopieren

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);
        }
    });
}
Nach dem Login kopieren

代码中没有类似一个 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);
Nach dem Login kopieren

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

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;
})
Nach dem Login kopieren

Das obige ist der detaillierte Inhalt vonWie Sie ein vollständiges Versprechen umsetzen. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Verwandte Etiketten:
Quelle:php.cn
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage