• 细说Promise


    一、前言

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

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

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

    复制代码
    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到底是何方圣神,具体使用法则是什么呢?我们就一起来探究探究。

    该篇博客原文地址:http://www.cnblogs.com/giggle/p/5575157.html

    二、Promise概述

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

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

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

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

      3、 rejected:异步操作失败。

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

      1、异步操作从pending到resolved;

      2、异步操作从pending到rejected;

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

     

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

    三、Promise入门指南

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

    var p = new Promise();

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

    怎么报错了呢?

    哦,对了,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截图如下:

    特别提醒:当传入匿名函数作为构造函数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参数一一对应。执行代码,结果如下:

    好了,当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截图如下:

     

    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截图如下:

     

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

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

     View Code

    执行上述代码,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;
                }
            }
        }
    };
    复制代码

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

     测试代码

    仔细品味,上述实现的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;
            };
        }
    }
    复制代码

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

     测试代码

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

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

  • 相关阅读:
    【洛谷】P2880 [USACO07JAN]平衡的阵容Balanced Lineup(st表)
    【洛谷】P1052 过河(状压dp)
    【洛谷】P1541 乌龟棋(四维背包dp)
    【BZOJ】4721: [Noip2016]蚯蚓 / 【洛谷】P2827 蚯蚓(单调队列)
    【洛谷】P1064 金明的预算方案(dp)
    【洛谷】P3908 异或之和(异或)
    【洛谷】P2434 [SDOI2005]区间(暴力)
    【洛谷】P2694 接金币(排序)
    【BZOJ】1012: [JSOI2008]最大数maxnumber /【洛谷】1198(线段树)
    【游记】noip2017酱油记
  • 原文地址:https://www.cnblogs.com/yulei126/p/6756204.html
Copyright © 2020-2023  润新知