Maison  >  Article  >  interface Web  >  Une introduction détaillée aux promesses en JavaScript

Une introduction détaillée aux promesses en JavaScript

黄舟
黄舟original
2017-03-04 15:30:151450parcourir

1. Introduction

JavaScript est monothread et ne peut exécuter qu'une seule tâche à la fois. Lorsqu'une tâche prend beaucoup de temps, les tâches suivantes doivent attendre. Alors, y a-t-il un moyen de résoudre ce genre de problème ? (Laissez de côté WebWorker), c'est-à-dire laisser le code s'exécuter de manière asynchrone. Qu'est-ce que cela signifie ? Par exemple, lors d'une requête asynchrone Ajax, la valeur de readyState est surveillée en permanence pour déterminer l'exécution de la fonction de rappel spécifiée.

Il existe généralement trois types d'exécution asynchrone, les fonctions de rappel, l'écoute d'événements et la publication et l'abonnement. L'écoute d'événements, la publication et l'abonnement sont en fait similaires, mais cette dernière est plus robuste.

Telle que la fonction de rappel, la fonction de rappel est l'idée de programmation la plus simple appliquée dans l'exécution asynchrone. Comme suit :

function async(item,callback){
    console.log(item);
    setTimeout(function(){
        callback(item+1);
    },1000);    
}

Dans l'exemple ci-dessus, lorsque la fonction asynchrone est exécutée, l'opération d'impression est terminée et la fonction de rappel est exécutée après 1 seconde (mais pas nécessairement 1 seconde, pour plus de détails, voir "choses setTimeout" ).

L'objectif principal de l'asynchrone est de gérer le non-blocage et d'améliorer les performances. Imaginez si une opération nécessite plusieurs fonctions asynchrones, comme suit :

async(1, function(item){
    async(item, function(item){
        async(item, function(item){
            console.log('To be continued..');
        });
    });
});

N'est-ce pas un peu difficile à lire ?

Pour un autre exemple, afin de rendre le code ci-dessus plus robuste, nous pouvons ajouter la capture d'exception. En mode asynchrone, la gestion des exceptions est répartie dans différentes fonctions de rappel. Nous ne pouvons pas gérer les exceptions via try...catch lors de l'appel, il est donc difficile de le faire de manière efficace et claire.

Hé, que dois-je faire ?

Boum boum boum, boum boum boum – Promesse fait ses débuts.

Si vous utilisez ES6 Promise pour optimiser le code ci-dessus, vous pouvez obtenir :

function opration(item){
    var p = new Promise(function(resolve, reject){
        setTimeout(function(){
            resolve(item+1);
        },1000);
    });
    console.log(item);
    return p;
}
function failed(e){
    console.log(e);
}
Promise.resolve(1).then(opration).then(opration).then(opration).catch(failed);

En utilisant Promise pour optimiser le code, l'avantage est évident, en tournant le fonction de rappel dans une chaîne La méthode de rappel évite les couches d'imbrication, ce qui rend le flux du programme clair et clair, et les erreurs générées par une ou plusieurs fonctions de rappel sont traitées uniformément via la méthode catch.

Oups, c'est bien. Alors, qui est cette promesse dans ES6 et quelles sont les règles d'utilisation spécifiques ? Explorons-le ensemble.

2. Présentation de Promise

Promise est une solution de programmation asynchrone, plus raisonnable que les solutions traditionnelles (rappels et événements) et plus puissant. Il a d'abord été proposé et implémenté par la communauté ES6 l'a écrit dans le standard de langage, a unifié son utilisation et a fourni les objets Promise de manière native. L'objet

Promesse a et seulement trois états :

1, en attente : l'opération asynchrone n'est pas terminée.

2. résolu : L'opération asynchrone est terminée.

3. rejeté : l'opération asynchrone a échoué.

De plus, il n'y a que deux modes de changement dans ces trois états, et une fois que l'état change, il ne changera plus :

1 Fonctionnement asynchrone depuis l'attente. à résolu ;

2. Opération asynchrone de en attente à rejetée

D'accord, puisqu'elle appartient à la spécification ES6, nous pouvons directement imprimer la promesse via Chrome et jetez un œil à ce truc :

Eh bien, Il est clair d'un coup d'œil que Promise est un constructeur, Ook, donc à travers lui, nous pouvons instancier notre propre objet Promise et utilisez-le.

3. Guide de démarrage avec Promise

Puisque Promise est un constructeur, jetons d'abord un coup d'œil au nouveau. Comme suit :

var p = new Promise();

et exécutez le code ci-dessus, la capture d'écran Chrome est la suivante :

Pourquoi l'erreur est-elle signalée ?

Oh, au fait, le constructeur Promise nécessite une fonction comme paramètre, et la fonction en tant que paramètre a deux paramètres. La fonction du premier paramètre est lorsque l'opération asynchrone passe de en attente à résolue. il doit être appelé quand ; le deuxième paramètre est qu'il doit être appelé lorsque l'opération asynchrone passe de en attente à rejetée.

Par exemple, j'ai nommé le premier paramètre de la fonction anonyme solve ; le deuxième paramètre a été nommé rejet. La démo est la suivante :

var p = new Promise(function(resolve, reject){
    console.log('new一个Promise对象');
    setTimeout(function(){
        resolve('Monkey');
    },1000);
});

et exécutez le code ci-dessus. La capture d'écran Chrome est la suivante :

Rappel spécial : <.>Lors du passage d'une fonction anonyme En tant que paramètre du constructeur Promise, la fonction anonyme a déjà été exécutée lorsque nous en créons une nouvelle, comme le montre la figure ci-dessus.

Hé, dans le code ci-dessus, nous utilisons le timer setTimeout dans la fonction anonyme et appelons la résolution après 1 seconde. Pourquoi n'y a-t-il pas d'erreur non définie ou signalée ? !

C'est le pouvoir de la Promesse. Pour cette raison, nous pouvons réécrire les opérations asynchrones en appels de chaîne élégants. Comment l'appeler ?

Vous vous souvenez que dans la section « Présentation de la promesse », nous avons imprimé la promesse via Chrome et utilisé la zone dans le cadre de la ligne rouge ? Parmi eux, il existe une méthode then (Promise.prototype.then) dans le prototype Promise. Grâce à cette méthode then, cela suffit. Comme suit :

p.then(function(value){
    console.log(value);
});
Parmi elles, la méthode then a deux fonctions anonymes comme paramètres, qui correspondent aux paramètres de résolution et de rejet de Promise. Exécutez le code et les résultats sont les suivants :

好了,当then执行完后,如果我们想继续在其之后看,使用then方法链式调用,有两种情况,一种是直接返回非Promise对象的结果;另一种是返回Promise对象的结果。

1、返回非Promise对象的结果:紧跟着的then方法,resolve立刻执行。并可使用前一个then方法返回的结果。如下:

p.then(function(value){
    console.log(value);
    //返回非Promise对象,如我的对象
    return {
        name: &#39;Dorie&#39;,
        age: 18
    };
}).then(function(obj){
    console.log(obj.name);
});

执行上述完整代码,chrome截图如下:

2、返回Promise对象的结果:紧跟着的then方法,与new Promise后的then方法一样,需等待前面的异步执行完后,resolve方可被执行。如下:

p.then(function(value){
    var p = new Promise(function(resolve, reject){
        setTimeout(function(){
            var message = value + &#39; V Dorie&#39;
            resolve(message);
        },1000);
    });
    console.log(value);
    //返回一个Promise对象
    return p;
}).then(function(value){
    console.log(value);
});

执行上述完整代码,chrome截图如下:

那么,当创建、执行Promise方法中有异常报错,如何捕获呢?

Promise.prototype.catch原型方法,就是为其而设定的。它具有冒泡的特性,比如当创建Promise实例时,就出错了,错误消息就会通过链式调用的这条链,一直追溯到catch方法,如果找到尽头都没有,就报错,并且再找到catch之前的所有then方法都不能执行了。Demo如下(代码太长,请自行展开):

<!DOCTYPE html>
    <head>
        <title>test</title>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    </head>
    <body>
        <script>
            var p = new Promise(function(resolve, reject){
                //M未定义
                console.log(M);
                setTimeout(function(){
                    resolve(&#39;Monkey&#39;);
                },1000);
            });
            p.then(function(value){
                var p = new Promise(function(resolve, reject){
                    setTimeout(function(){
                        var message = value + &#39; V Dorie&#39;
                        resolve(message);
                    },1000);
                });
                console.log(value);
                //返回一个Promise对象
                return p;
            }).then(function(value){
                console.log(value);
                return &#39;next is catch&#39;;
            }).catch(function(e){
                console.log(e);
            }).then(function(value){
                console.log(&#39;execute,but value is &#39; + value);
            });
        </script>
    </body>
</html>

执行上述代码,chrome截图如下:

好了,到这里,我们已经了解了最常用的Promise.prototype.then和Promise.prototype.catch这两个原型方法。另外,像Promise构造函数还有属于自身的方法,如all、rece、resolve、reject等,详情请点击这里(here)。

通过一路上对Promise的讲述,我们也有了一定的认识,其实Promise并没有想象中的那么难以理解嘛。懂得Promise概念后,其实我们自己也可以实现一个简易版的Promise。下面就一同尝试实现一个呗。

四、模拟Promise

假设:有三个异步操作方法f1,f2,f3,且f2依赖于f1,f3依赖于f2。如果,我们采用ES6中Promise链式调用的思想,我们可以将程序编写成这样:

f1().then(f2).then(f3);

那么,通过上面这一系列链式调用,怎样才能达到与ES6中Promise相似的功能呢?

初步想法:首先将上述链式调用的f2、f3保存到f1中,当f1中的异步执行完后,再调用执行f2,并将f1中的f3保存到f2中,最后,等f2中的异步执行完毕后,调用执行f3。详细构思图,如下:

从上图可知,由于f1、f2 、f3是可变得,所以存储数组队列thens,可放入,我们即将创建的模拟Promise构造函数中。具体实现代码如下:

//模拟Promise
function Promise(){
    this.thens = [];
};
Promise.prototype = {
    constructor: Promise,
    then: function(callback){
        this.thens.push(callback);
        return this;        
    }
};

并且,需要一个Promise.prototype.resolve原型方法,来实现:当f1异步执行完后,执行紧接着f1后then中的f2方法,并将后续then中方法,嫁接到f2中,如f3。具体实现代码如下:

//模拟Promise,增加resolve原型方法
function Promise(){
    this.thens = [];
};
Promise.prototype = {
    constructor: Promise,
    then: function(callback){
        this.thens.push(callback);
        return this;        
    },
    resolve: function(){
        var t = this.thens.shift(), 
            p;
        if(t){
            p = t.apply(null,arguments);
            if(p instanceof Promise){
                p.thens = this.thens;
            }
        }
    }
};

测试代码(代码太长,自行打开并运行)。

function f1() {
    var promise = new Promise();
    setTimeout(function () {

        console.log(1);
        promise.resolve();
    }, 1500)

    return promise;
}

function f2() {
    var promise = new Promise();
    setTimeout(function () {
        console.log(2);
        promise.resolve();
    }, 1500);
    return promise;
}

function f3() {
    var promise = new Promise();
    setTimeout(function () {

        console.log(3);
        promise.resolve();
    }, 1500)

    return promise;
}
f1().then(f2).then(f3);

仔细品味,上述实现的Promise.prototype.resolve方法还不够完美,因为它只能够满足于f1、f2、f3等方法都是使用模拟的Promise异步执行的情况。而,当其中有不是返回的Promise对象的呢,而是返回一个数字,亦或是什么也不返回,该怎么办?所以,针对以上提出的种种可能,再次改进resolve。改善代码如下:

//模拟Promise,改善resolve原型方法
var Promise = function () {
    this.thens = [];
};
Promise.prototype = {
    constructor: Promise,
    then: function(callback){
        this.thens.push(callback);
        return this;        
    },
    resolve: function () {
        var t,p;
        t = this.thens.shift();
        t && (p = t.apply(null, arguments));
        while(t && !(p instanceof Promise)){
            t = this.thens.shift();
            t && (p = t.call(null, p));    
        }
        if(this.thens.length){
            p.thens = this.thens;
        };
    }
}

测试代码(代码太长,自行打开并运行)。

function f1() {
    var promise = new Promise();
    setTimeout(function () {

        console.log(1);
        promise.resolve();
    }, 1500)

    return promise;
}

function f2() {
    var promise = new Promise();
    setTimeout(function () {
        console.log(2);
        promise.resolve();
    }, 1500);
    return promise;
}

function f3() {
    var promise = new Promise();
    setTimeout(function () {

        console.log(3);
        promise.resolve();
    }, 1500)

    return promise;
}

function f4() {
    console.log(4);
    return 11;
}

function f5(x) {
    console.log(x+1);
}

function f6() {
    var promise = new Promise();
    setTimeout(function () {

        console.log(6);
        promise.resolve();
    }, 1500)

    return promise;
}

function f7() {
    console.log(7);
}

var that = f1().then(f2).then(f3).then(f4).then(f5).then(f6).then(f7);

好了,初步模拟的Promise就OK啦。

吼吼,对于Promise,我们这一路走来,发现原来也不过如此呢。

 以上就是详细介绍JavaScript 中的 Promise的内容,更多相关内容请关注PHP中文网(www.php.cn)!

Déclaration:
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