Maison > interface Web > js tutoriel > Comment mettre en œuvre une promesse complète

Comment mettre en œuvre une promesse complète

一个新手
Libérer: 2018-05-15 17:34:06
original
2455 Les gens l'ont consulté

J'ai utilisé Promise, mais j'ai toujours l'impression de ne pas le comprendre. J'ai lu de nombreux articles, mais je ne comprends toujours pas le principe de mise en œuvre de Promise. Plus tard, j'ai lu les articles et débogué le code, et finalement j'ai lentement commencé à le comprendre, mettons en œuvre une promesse selon notre propre compréhension.

Pour bien comprendre le code, vous devez comprendre la signification de ceci et de la fermeture .

Qu'est-ce que Promise

En termes simples, Promise vise principalement à résoudre le problème des rappels asynchrones. L'utilisation de Promise pour gérer les rappels asynchrones rend la hiérarchie du code claire, facile à comprendre et à maintenir. Ses spécifications principales sont actuellement principalement Promesses/A+. Si vous n'êtes pas familier avec l'utilisation de Promise, vous pouvez vous référer à mon article – notes d'étude es6 5 – promesse. Si vous le comprenez, alors lisez cet article, il vous sera d'une grande aide.

Avant de commencer, écrivons un scénario d’application de promesse pour découvrir le rôle des promesses. Google et Firefox prennent actuellement en charge les promesses es6. Nous utilisons setTimeout pour simuler un fonctionnement asynchrone. Le code spécifique est le suivant :

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);
});
Copier après la connexion

En fin de compte, la promesse que nous avons écrite peut également réaliser cette fonction.

Construction initiale

Écrivons une simple promesse. Le paramètre de Promise est la fonction fn. Passez la méthode de résolution définie en interne comme paramètre à fn et appelez fn. Lorsque l'opération asynchrone réussit, la méthode de résolution sera appelée, puis la fonction de rappel enregistrée dans then sera exécutée.

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

Ajouter la prise en charge du chaînage

Ajoutez le chaînage ci-dessous, et la méthode de rappel réussie doit être convertie en tableau avant de pouvoir être stockée. En même temps, nous ajoutons des paramètres à la méthode de résolution afin qu'undéfini ne soit pas affiché.

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);
}
Copier après la connexion
  • promesse = ceci, nous n'avons donc pas à nous soucier du changement soudain de ce pointeur à un moment donné.

  • Appelez la méthode then et placez le rappel dans la file d'attente promise._resolves

  • <🎜 ; >
  • Lors de la création de l'objet Promise, appelez son fn et transmettez la méthode de résolution Lorsque l'opération asynchrone de fn est exécutée avec succès, la résolution sera appelée, c'est-à-dire <span style="font-family:times new roman,times; color:#000000">. promise._resolves<code><span style="color:#000000">promise._resolves</span> rappel dans la colonne

  • <code><span style="color:#000000">resolve 方法</span>méthode de résolution reçoit un paramètre sont les résultats renvoyés par les opérations asynchrones, ce qui facilite la transmission des valeurs.

  • Le retour this dans la méthode then implémente les appels en chaîne.

Cependant, il y a encore quelques problèmes avec la promesse actuelle. Si je transmets une fonction qui ne contient pas d'opérations asynchrones, la résolution sera exécutée avant cette date, c'est-à-dire pour. disons que promise._resolves est un tableau vide.

resolveAfin de résoudre ce problème, nous pouvons ajouter setTimeout dans la résolution pour placer la logique d'exécution du rappel dans à la fin de la file d'attente des tâches JS.

    function resolve(value) {
        setTimeout(function() {
            promise._resolves.forEach(function (callback) {
                callback(value);
            });
        },0);
    }
Copier après la connexion

Introduction de l'état

Analyse des bases de Promise Il y a ici un problème : si l'opération asynchrone Promise a réussi, le rappel enregistré d'ici là ne sera plus jamais exécuté, ce qui n'est pas conforme à nos attentes.

Je ne comprends pas bien cette phrase. Si vous savez quelque chose, vous pouvez laisser un message. Il est préférable de donner un exemple. Mais je pense personnellement que les rappels enregistrés seront alors ajoutés au tableau avant l'exécution de la résolution, il n'y aura donc pas de non-exécution.

Suivez les étapes ci-dessus et introduisez les états :
<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>
Copier après la connexion

Chaque promesse a trois états mutuellement exclusifs : en attente, remplie et rejetée. Il n’existe que deux possibilités pour que l’état d’un objet Promise change : de en attente à exécuté et de en attente à rejeté. Tant que ces deux situations se produiront, l’État se solidifiera et ne changera plus, et maintiendra ce résultat. Même si le changement a déjà eu lieu, si vous ajoutez une fonction de rappel à l'objet Promise, vous obtiendrez le résultat immédiatement. C'est complètement différent d'un événement. La caractéristique d'un événement est que si vous le manquez et l'écoutez à nouveau, vous n'obtiendrez pas le résultat.

Ajouter la transmission des résultats asynchrones

La méthode d'écriture actuelle ne prend pas en compte la transmission des résultats renvoyés de manière asynchrone :
    function resolve(value) {
        setTimeout(function(){
            promise._status = "FULFILLED";
            promise._resolves.forEach(function (callback) {
                value = callback(value);
            })
        },0);
    }
Copier après la connexion

Promesse série.

La promesse en série signifie qu'une fois que la promesse actuelle a atteint l'état rempli, la prochaine promesse (la prochaine promesse) commence. Par exemple, nous utilisons d'abord ajax pour obtenir les données de l'utilisateur en arrière-plan, puis obtenons d'autres données basées sur ces données.

Ici, nous modifions principalement la méthode 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);
            }
        })
        
    };
Copier après la connexion

La méthode then nécessite beaucoup de changements, laissez-moi l'expliquer ici : <🎜>
  • 注意的是,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)})
Copier après la connexion

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);
            }
        })
        
    };
Copier après la connexion

在 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);
    }
Copier après la connexion

添加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);
          }
      });
}
Copier après la connexion

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);
        }
    });
}
Copier après la connexion

代码中没有类似一个 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);
Copier après la connexion

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

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;
})
Copier après la connexion

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Étiquettes associées:
source:php.cn
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal