细说 JavaScript 中的 Promise

澳门新葡亰3522平台游戏 8

一、前言

JavaScript是单线程的,固,一次只能执行一个任务,当有一个任务耗时很长时,后面的任务就必须等待。那么,有什么办法,可以解决这类问题呢?(抛开WebWorker不谈),那就是让代码异步执行嘛。什么意思,如Ajax异步请求时,就是通过不断监听readyState的值,以确定执行指定的回调函数。

澳门新葡亰3522平台游戏,通常的异步执行有三种,回调函数、事件监听以及发布订阅,其中事件监听和发布订阅其实差不多,只是后者更加健壮一些。

如回调函数,回调函数是应用在异步执行中最简单的编程思想。如下:

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

在上述列子中,执行async函数时,完成打印操作,并在1秒后执行callback回调函数(但不一定是1秒,详情见”setTimeout那些事儿”)。

异步的主要目的就是处理非阻塞,提升性能。想象一下,如果某个操作需要经过多个async函数操作呢,如下:

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

是不是有点不易阅读了?

再比如,为了让上述代码更加健壮,我们可以加入异常捕获。在异步的方式下,异常处理分布在不同的回调函数中,我们无法在调用的时候通过try…catch的方式来处理异常,
所以很难做到有效,清楚。

哎哟喂,那可怎么办呢?

噔噔噔噔,噔噔噔噔—Promise闪亮登场。

倘若用ES6的Promise优化上述代码,可得:

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);

Promise 优化后的代码,优点显而易见,让回调函数变成了链式调用,避免了层层嵌套,使程序流程变得清晰明朗,并为一个或者多个回调函数抛出的错误通过catch方法进行统一处理。

哎呦,不错嘛,那这个ES6中的Promise到底是何方圣神,具体使用法则是什么呢?我们就一起来探究探究。

ES6中的promise对象很早就听说过,据说是为了解决我们使用回调产生回调地狱的问题。今天下午既然有这么想学的欲望,就来看一看吧,当然参考的还是阮一峰老师的教程。

二、Promise概述

Promise是异步编程的一种解决方案,比传统的解决方案(回调和事件)更合理和更强大。它由社区最早提出和实现,ES6将其写进了语言标准,统一了用法,原生提供了Promise对象。

Promise对象有且只有三种状态:

1、 pending:异步操作未完成。

2、 resolved:异步操作已完成。

3、 rejected:异步操作失败。

又,这三种状态的变化只有两种模式,并且一旦状态改变,就不会再变:

1、异步操作从pending到resolved;

2、异步操作从pending到rejected;

好了,既然它是属于ES6规范,我们再通过chrome,直接打印出Promise,看看这玩意:

澳门新葡亰3522平台游戏 1

恩,一目了然,Promise为构造函数,欧克,这样通过它,我们就可以实例化自己的Promise对象了,并加以利用。

第一部分:什么是Promise

三、Promise入门指南

Promise既然是一个构造函数,那么我们就先new一个看看。如下:

var p = new Promise();

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

澳门新葡亰3522平台游戏 2

怎么报错了呢?

哦,对了,Promise构造函数,需要一个函数作为其参数哦,并且作为参数的函数中,有两个参数,第一个参数的作用为,当异步操作从pending到resolved时,供其调用;第二个参数的作用为,当异步操作从pending到rejected时,供其调用。

例如,我将匿名函数的第一个参数取名为resolve;第二个参数取名为reject。Demo如下:

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

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

澳门新葡亰3522平台游戏 3

特别提醒:当传入匿名函数作为构造函数Promise的参数时,我们在new的时候,匿名函数就已经执行了,如上图。

咦,上述代码中,我们在匿名函数中,通过setTimeout定时器,在1秒后,还调用了resolve呢,怎么没有报undefined或者错误呢?!

这就是Promise强大之处的一点。正因为这样,我们就可以将异步操作改写成优雅的链式调用。怎么调用呢?

还记得,我们在“Promise概述”一小节中,通过chrome打印Promise,用红线框中的区域么?其中,Promise原型中有一then方法(Promise.prototype.then),通过这个then方法,就可以了。如下:

p.then(function(value){
    console.log(value);
});

其中,then方法有两个匿名函数作为其参数,与Promise的resolve和reject参数一一对应。执行代码,结果如下:

澳门新葡亰3522平台游戏 4

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

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

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

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

澳门新葡亰3522平台游戏 5

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

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

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

澳门新葡亰3522平台游戏 6

那么,当创建、执行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('Monkey');
                },1000);
            });
            p.then(function(value){
                var p = new Promise(function(resolve, reject){
                    setTimeout(function(){
                        var message = value + ' V Dorie'
                        resolve(message);
                    },1000);
                });
                console.log(value);
                //返回一个Promise对象
                return p;
            }).then(function(value){
                console.log(value);
                return 'next is catch';
            }).catch(function(e){
                console.log(e);
            }).then(function(value){
                console.log('execute,but value is ' + value);
            });
        </script>
    </body>
</html>

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

澳门新葡亰3522平台游戏 7

好了,到这里,我们已经了解了最常用的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。详细构思图,如下:

澳门新葡亰3522平台游戏 8

从上图可知,由于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,我们这一路走来,发现原来也不过如此呢。

Promise是ES6中的一个内置的对象(实际上是一个构造函数,通过这个构造函数我们可以创建一个Promise对象),它是为了解决异步问题的。Promise的英文意思是承诺。

Promise的特点如下:

•Promise有三种状态:Pending、Rejected。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这就是Promise。

•Promise一共有三种状态,但是他们之间是如何转化的呢? 其一:
从Pending。其二: 从Pending。
且只有这两种形式的转变,即使是Promise对象的结果也无力回天了。

但是Promise也是有一定的缺点的,如在Pengding时,我们无法取消状态,另外,我们没法判断Pending究竟是刚刚开始的Pending还是即将要完成的Pending。

第二部分:使用Promise

在下面例子的讲解中,我们需要使用到setTimeout函数,这里首先说明setTimeout的一些重要用法。一般我们常用的如下所示:

setTimeout;

即在1000ms之后执行func函数,但是其实setTimeout还可以传入更多的参数。这篇博客做了讲解,而这里为了了解下面的例子我们只需要知道,在chrome中可以传入第三个、第四个参数….作为func()函数的参数传递进去,举例如下:

setTimeout { console.log; }, 1000, 20, 50);

最终的输入结果是:70

下面的代码创建了一个Promise实例:

var promise = new Promise(function { // ... some code if { resolve; } else { reject;

其中,由于Promise是构造函数,所以我们使用new来创建一个对象即可,
值得注意的是:function{}这个函数是必须要写的,否则就不是Promise了。

这个函数是为了初始化Promise对象,其中这个函数接受了两个函数作为参数,
如果在函数体中我们执行了resolve函数,那么Promise的状态就会由pending转化为resolved,类似的,如果我们执行了reject函数,pending就会变成
rejected。

注意:
这个例子的if语句不是必要的,这里想要表达的意思是如果得到了异步成功的相关结果,我们就将调用resolve,将pending转化为resolved,并且将异步成功后的value值传递进去以便后面使用,说是以便后面使用是因为Promise还有一个then之后需要做的事情。这也就是resolve和reject内置函数存在的意义了。

当创建了这个Promise对象之后,就一定会有一个结果了,但是成功和失败还是不确定的,我们需要根据判断结果的成功和失败来做不同的事情,于是用到了then()方法,如下所示:

promise.then { // success}, function;

下面是一个例子,并做了详尽的说明:

 testsettimeout  var promise = new Promise(function  { console.log; var a = 10; var b = a + 25; if  { // &#19968;&#26086;&#24322;&#27493;&#25191;&#34892;&#25104;&#21151;&#65292;&#25105;&#20204;&#23601;&#35843;&#29992;&#20869;&#32622;&#30340; resolve&#20989;&#25968;&#65292;&#23558;pending&#29366;&#24577;&#36716;&#21270;&#20026;resolved&#65292;&#24182;&#19988;&#20256;&#20837;&#25105;&#20204;&#24076;&#26395;&#20256;&#20986;&#30340;&#25191;&#34892;&#25104;&#21151;&#21518;&#30340;&#32467;&#26524;&#12290; // &#27880;&#24847;&#65306; &#36825;&#37324;&#19968;&#26086;&#29366;&#24577;&#36716;&#21464;&#65292;&#37027;&#20040;&#21518;&#38754;&#23601;&#19968;&#23450;&#20250;&#35843;&#29992;then&#26041;&#27861;&#20013;&#30340;&#31532;&#19968;&#20010;&#21442;&#25968;&#30340;&#20989;&#25968;&#65292;&#28982;&#21518;&#23558;&#25105;&#20204;&#20256;&#20837;&#32473;resolve&#30340;&#21442;&#25968;&#20256;&#32473;then&#26041;&#27861;&#20013;&#30340;&#31532;&#19968;&#20010;&#26041;&#27861;&#20316;&#20026;&#21442;&#25968;&#65292;&#25105;&#20204;&#23601;&#21487;&#20197;&#22312;then&#30340;&#31532;&#19968;&#20010;&#26041;&#27861;&#20013;&#20351;&#29992;&#20102;&#12290; resolve; } else { reject; promise.then { console.log("&#24322;&#27493;&#25191;&#34892;&#25104;&#21151;&#65292;&#36755;&#20986;&#25191;&#34892;&#32467;&#26524;&#65306;" + value); }, function  { console.log("&#24322;&#27493;&#25191;&#34892;&#22833;&#36133;&#65292;&#36755;&#20986;&#25191;&#34892;&#32467;&#26524;&#65306;" + error); }); 

而阮一峰老师所列的下面的这个例子可以清楚的看出promise就是异步的:

 let promise = new Promise(function { console.log; }); promise.then { console.log; console.log;

最终的输出结果是: Promise Resolved Hi

分析:
从这个例子中可以看出promise的异步,因为前面的两部分代码还没有执行,就已经输出了Hi。另外可以确定的是
Resolved 一定是在 Promise之后输出的,这个顺序是不可能有问题的。

下面的例子是一个异步添加图片的url的例子

function loadImageAsync { return new Promise(function { var image = new Image(); image.onload = function ; }; image.onerror = function() { reject(new Error('Could not load image at' + url)); } image.src = url; }); }

如果加载成功就使用resolve方法,如果失败就使用reject方法。

下面的例子是阮一峰老师封装的Ajax的例子,是在太好,没法不直接拿来参考~

var getJSON = function { var promise = new Promise(function{ var client = new XMLHttpRequest(); client.open; client.onreadystatechange = handler; client.responseType = "json"; client.setRequestHeader("Accept", "application/json"); client.send { if (this.readyState !== 4) { return; } if  { resolve; } else { reject(new Error; } }; }); return promise;};getJSON.then { console.log;}, function { console.error;

这里应该大家都可以看懂,值得注意的是:handler这个处理函数的使用在这里显得很巧妙。

第三部分: Promise.prototype.then()

在上一部分,我们实际上已经介绍了then()方法,而这里需要强调的有两点。

第一: then()方法是Promise原型上定义的方法。

第二:then方法调用后返回的结果会传给下一个then方法中。

第一:我们再chrome中输入 Promise.prototype可以看到下面的例子:

可以看出在Promise的原型中确实是由then方法的。(注:比如我们想看Array这个内置对象有哪些方法,我们就可以直接在chrome中输入Array.prototype,然后就可以看到对应方法的列表了)

第二:
then()的作用是为Promise实例添加状态改变时的回调函数。前面说过,then方法的第一个参数是Resolved状态的回调函数,第二个参数是Rejected状态的回调函数。

then()由于支持链式调用,所以也可以写成下面这样:

getJSON.then { return json.post;}).then { // ...});

第一个回调函数完成以后,会将返回结果作为参数,传入第二个回调函数。

getJSON.then { return getJSON.then(function funcA { console.log("Resolved: ", comments);}, function funcB{ console.log;

即第一个then又返回了一个promise,如何这个promise的状态变成了
Resolved,那么就会执行第二个then的第一个函数, 如果变成了
Rejected,就会执行第二个第二个函数。

第四部分: Promise.prototype.catch()

Promise.prototype.catch()方法实际上是then方法的别名,
这里使用catch()纯粹是为了便于使用和理解。

getJSON.then { // ...}).catch { // 处理 getJSON 和 前一个回调函数运行时发生的错误 console.log;

在之前的例子中,我们讲解then()方法接受两个参数,第一个参数是pending变成resolved之后执行的函数,它是必选的;
第二个参数是pending变成rejected之后执行的函数,它是可选的。