zoukankan      html  css  js  c++  java
  • Promise机制

     Promise的诞生与Javascript中异步编程息息相关,js中异步编程主要指的是setTimout/setInterval、DOM事件机制、ajax,通过传入回调函数实现控制反转。异步编程为js带来强大灵活性的同时,也带来了嵌套回调的问题。详细来说主要有两点,第一嵌套太深代码可读性太差,第二并行逻辑必须串行执行。

    request = function(url, cb, eb) {
        var xhr = new XMLHttpRequest();
        xhr.onreadystatechange = function() {
            if (xhr.readyState === 4) {
                if ((xhr.status >=200 && xhr.status < 300) || xhr.status === 304) {
                    cb(xhr.responseText);
                } else {
                    eb(new Error({
                        message: xhr.status
                    }));
                }
            }
        };
        xhr.open('get', url, true);
        xhr.send(null);
    }
    

      

      这个例子中程序要依次处理data1、data2、data3,嵌套太多可读性太差

    request = function(url) {
        var def = new Deferred();
    
        var xhr = new XMLHttpRequest();
        xhr.onreadystatechange = function() {
            if (xhr.readyState === 4) {
                if ((xhr.status >=200 && xhr.status < 300) || xhr.status === 304) {
                    def.resolve(xhr.responseText)
                } else {//简化ajax,没有提供错误回调
                    def.reject(new Error({
                        message: xhr.status
                    }));
                }
            }
        };
        xhr.open('get', url, true);
        xhr.send(null);
    
        return def.promise;
    }
    
    request('data1.json').then(function(data1) {
        console.log(data1);//处理data1
        return request('data2.json');
    }).then(function(data2) {
        console.log(data2);//处理data2
        return request('data3.json');
    }, function(err) {
        console.error(err);
    }).then(function(data3) {
        console.log(data3);
        alert('success');
    }, function(err) {
        console.error(err);
    });
    

      

      这个例子中程序需要请求data1、data2、data3数据,得到三个数据后才进行下一步处理。数据并不需要串行请求,但我们的代码却需要串行执行,增加了等待时间。

    //并行逻辑串行执行
    request('data1', function(data1) {
        request('data2', function(data2) {
            request('data3', function(data3) {
                console.log(data1, data2, data3);//处理全部数据
    
                alert('success');
            }, function(err) {
                console.error(err);
            });
        }, function(err) {
            console.error(err);
        });
    }, function(err) {
        console.error(err);
    });

      

    Promise机制

      Promise机制便是上述问题的一种解决方案。与他相关的规范有PromiseAPromiseA+,PromiseA中对Promise进行了整体描述,PromiseA+对A进行了补充,在then函数的行为方面进行了更加详尽的阐述。

    PromiseA+规范
    promise represents the eventual result of an asynchronous operation. 
    一个promise代表了一个异步操作的最终结果
    The primary way of interacting with a promise is through its then method, which registers callbacks to receive either a promise’s eventual value or the reason why the promise cannot be fulfilled.跟promise交互的主要方式是通过他的then方法来注册回调函数去接收promise的最终结果值或者是promise不能完成的原因。

      我们可以简单总结一下规范。每个promise都有三个状态:pending(默认)、fulfilled(完成)、rejected(失败);默认状态可以转变为完成态或失败态,完成态与失败态之间无法相互转换,转变的过程是不可逆的,转变一旦完成promise对象就不能被修改。通过promise提供的then函数注册onFulfill(成功回调)、onReject(失败回调)、onProgres(进度回调)来与promise交互。Then函数返回一个promise对象(称为promise2,前者成为promise1),promise2受promise1状态的影响,具体请查看A+规范。

      上两个规范中并没有说明promise的状态如何改变,大部分前端框架中使用Deferred来改变promise的状态(resolve()、reject())。二者关系请看下图。

      这里根据规范,我们实现一下promise

    Promise = function() {
        this.queue = [];
        this.value = null;
        this.status = 'pending';// pending fulfilled rejected
    };
    
    Promise.prototype.getQueue = function() {
        return this.queue;
    };
    Promise.prototype.getStatus = function() {
        return this.status;
    };
    Promise.prototype.setStatus = function(s, value) {
        if (s === 'fulfilled' || s === 'rejected') {
            this.status = s;
            this.value = value || null;
            this.queue = [];
            var freezeObject = Object.freeze || function(){};
            freezeObject(this);// promise的状态是不可逆的
        } else {
            throw new Error({
                message: "doesn't support status: " + s
            });
        }
    };
    Promise.prototype.isFulfilled = function() {
        return this.status === 'fulfilled';
    };
    Promise.prototype.isRejected = function() {
        return this.status === 'rejected';
    }
    Promise.prototype.isPending = function() {
        return this.status === 'pending';
    }
    Promise.prototype.then = function(onFulfilled, onRejected) {
        var handler = {
            'fulfilled': onFulfilled,
            'rejected': onRejected
        };
        handler.deferred = new Deferred();
    
        if (!this.isPending()) {//这里允许先改变promise状态后添加回调
            utils.procedure(this.status, handler, this.value);
        } else {
            this.queue.push(handler);//then may be called multiple times on the same promise;规范2.2.6
        }
        return handler.deferred.promise;//then must return a promise;规范2.2.7
    };
    
    var utils = (function(){
        var makeSignaler = function(deferred, type) {
            return function(result) {
                transition(deferred, type, result);
            }
        };
    
        var procedure = function(type, handler, result) {
            var func = handler[type];
            var def = handler.deferred;
    
            if (func) {
                try {
                    var newResult = func(result);
                    if (newResult && typeof newResult.then === 'function') {//thenable
                        // 此种写法存在闭包容易造成内存泄露,我们通过高阶函数解决
                        // newResult.then(function(data) {
                        //     def.resolve(data);
                        // }, function(err) {
                        //     def.reject(err);
                        // });
                        //PromiseA+规范,x代表newResult,promise代表def.promise
                        //If x is a promise, adopt its state [3.4]:
                        //If x is pending, promise must remain pending until x is fulfilled or rejected.
                        //If/when x is fulfilled, fulfill promise with the same value.
                        //If/when x is rejected, reject promise with the same reason.
                        newResult.then(makeSignaler(def, 'fulfilled'), makeSignaler(def, 'rejected'));//此处的本质是利用了异步闭包
                    } else {
                        transition(def, type, newResult);
                    }
                } catch(err) {
                    transition(def, 'rejected', err);
                }
            } else {
                transition(def, type, result);
            }
        };
    
        var transition = function(deferred, type, result) {
            if (type === 'fulfilled') {
                deferred.resolve(result);
            } else if (type === 'rejected') {
                deferred.reject(result);
            } else if (type !== 'pending') {
                throw new Error({
                    'message': "doesn't support type: " + type
                });
            }
        };
    
        return {
            'procedure': procedure
        }
    })();
    
    Deferred = function() {
        this.promise = new Promise();
    };
    
    Deferred.prototype.resolve = function(result) {
        if (!this.promise.isPending()) {
            return;
        }
    
        var queue = this.promise.getQueue();
        for (var i = 0, len = queue.length; i < len; i++) {
            utils.procedure('fulfilled', queue[i], result);
        }
        this.promise.setStatus('fulfilled', result);
    };
    
    Deferred.prototype.reject = function(err) {
        if (!this.promise.isPending()) {
            return;
        }
    
        var queue = this.promise.getQueue();
        for (var i = 0, len = queue.length; i < len; i++) {
            utils.procedure('rejected', queue[i], err);
        }
        this.promise.setStatus('rejected', err);
    }
    

      通过Promise机制我们的编程方式可以变成这样:

    request = function(url) {
        var def = new Deferred();
    
        var xhr = new XMLHttpRequest();
        xhr.onreadystatechange = function() {
            if (xhr.readyState === 4) {
                if ((xhr.status >=200 && xhr.status < 300) || xhr.status === 304) {
                    def.resolve(xhr.responseText)
                } else {//简化ajax,没有提供错误回调
                    def.reject(new Error({
                        message: xhr.status
                    }));
                }
            }
        };
        xhr.open('get', url, true);
        xhr.send(null);
    
        return def.promise;
    }
    
    request('data1.json').then(function(data1) {
        console.log(data1);//处理data1
        return request('data2.json');
    }).then(function(data2) {
        console.log(data2);//处理data2
        return request('data3.json');
    }, function(err) {
        console.error(err);
    }).then(function(data3) {
        console.log(data3);
        alert('success');
    }, function(err) {
        console.error(err);
    });
    

      

      

      对于并行逻辑串行执行问题我们可以这样解决

    //所有异步操作都完成时,进入完成态,
    //其中一项异步操作失败则进入失败态
    all = function(requestArray) {
        // var some = Array.prototype.some;
        var def = new Deferred();
        var results = [];
        var total = 0;
        requestArray.some(function(r, idx) {
            //为数组中每一项注册回调函数
            r.then(function(data) {
                if (def.promise.isPending()) {
                    total++;
                    results[idx] = data;
    
                    if (total === requestArray.length) {
                        def.resolve(results);
                    }
                }
            },  function(err) {
                def.reject(err);
            });
            //如果不是等待状态则停止,比如requestArray[0]失败的话,剩下数组则不用继续注册
            return !def.promise.isPending();
        });
    
        return def.promise;
    }
    
    all(
        [request('data1.json'),
        request('data2.json'),
        request('data3.json')]
        ).then(
            function(results){
                console.log(results);// 处理data1,data2,data3
                alert('success');
        }, function(err) {
            console.error(err);
        });
    

      

      以下是几个测试案例

    //链式调用
    var p1 = new Deferred();
    p1.promise.then(function(result) {
        console.log('resolve: ', result);
        return result;
    }, function(err) {
        console.log('reject: ', err);
        return err;
    }).then(function(result) {
        console.log('resolve2: ', result);
        return result;
    }, function(err) {
        console.log('reject2: ', err);
        return err;
    }).then(function(result) {
        console.log('resolve3: ', result);
        return result;
    }, function(err) {
        console.log('reject3: ', err);
        return err;
    });
    p1.resolve('success');
    //p1.reject('failed');
    p1.promise.then(function(result) {
        console.log('after resolve: ', result);
        return result;
    }, function(err) {
        console.log('after reject: ', err);
        return err;
    }).then(function(result) {
        console.log('after resolve2: ', result);
        return result;
    }, function(err) {
        console.log('after reject2: ', err);
        return err;
    }).then(function(result) {
        console.log('after resolve2: ', result);
        return result;
    }, function(err) {
        console.log('after reject2: ', err);
        return err;
    });
    
    //串行异步
    var p2 = new Deferred();
    p2.promise.then(function(result) {
        var def = new Deferred();
        setTimeout(function(){
            console.log('resolve: ', result);
            def.resolve(result);
        })
        return def.promise;
    }, function(err) {
        console.log('reject: ', err);
        return err;
    }).then(function(result) {
        var def = new Deferred();
        setTimeout(function(){
            console.log('resolve2: ', result);
            def.reject(result);
        })
        return def.promise;
    }, function(err) {
        console.log('reject2: ', err);
        return err;
    }).then(function(result) {
        console.log('resolve3: ', result);
        return result;
    }, function(err) {
        console.log('reject3: ', err);
        return err;
    });
    p2.resolve('success');
    
    //并行异步
    var p1 = function(){
        var def = new Deferred();
        setTimeout(function() {
            console.log('p1 success');
            def.resolve('p1 success');
        }, 20);
    
        return def.promise;
    }
    var p2 = function(){
        var def = new Deferred();
        setTimeout(function() {
            console.log('p2 failed');
            def.reject('p2 failed');
        }, 10);
    
        return def.promise;
    }
    
    var p3 = function(){
        var def = new Deferred();
        setTimeout(function() {
            console.log('p3 success');
            def.resolve('p3 success');
        }, 15);
    
        return def.promise;
    }
    
    all([p1(), p2(), p3()]).then(function(results) {
        console.log(results);
    }, function(err) {
        console.error(err);
    });
    

      

    Promise优点

    对比使用Promise前后我们可以发现,传统异步编程通过嵌套回调函数的方式,等待异步操作结束后再执行下一步操作。过多的嵌套导致意大利面条式的代码,可读性差、耦合度高、扩展性低。通过Promise机制,扁平化的代码机构,大大提高了代码可读性;用同步编程的方式来编写异步代码,保存线性的代码逻辑,极大的降低了代码耦合性而提高了程序的可扩展性。 

    参考文章:

    javascript 异步编程

    jQuery 2.0.3 源码分析 Deferred(最细的实现剖析,带图)

    JavaScript异步编程原理

  • 相关阅读:
    Hadoop Avro支持多输入AvroMultipleInputs
    Java LinqCollection 仿Linq的list常用函数
    json转成java对象
    symbol lookup error: /lib64/libpango-1.0.so.0: undefined symbol: g_log_structured_standard 错误
    CentOS 6&7安装ffmpeg
    MySQL的ibdata1文件占用过大瘦身
    Centos下磁盘管理的常用命令记录(如查找大文件)
    EasyPOI 教程以及完整工具类的使用
    github最火的springboot开源学习资料
    微信机器人
  • 原文地址:https://www.cnblogs.com/zhuyang/p/4341685.html
Copyright © 2011-2022 走看看