zoukankan      html  css  js  c++  java
  • 异步编程之Promise(2):探究原理

    异步编程系列教程:

    1. (翻译)异步编程之Promise(1)——初见魅力
    2. 异步编程之Promise(2):探究原理
    3. 异步编程之Promise(3):拓展进阶
    4. 异步编程之Generator(1)——领略魅力
    5. 异步编程之Generator(2)——剖析特性
    6. 异步编程之co——源码分析

    动手实现Promise

    在异步编程之Promise(1)里,我是翻译了一篇文章,里面是探究promise的模式和领略它的魅力。我们可以利用promise,缓解回调函数给我们带来的回调金字塔。使用链式结构书写,使代码更加简洁易懂,易于控制。但是对于构造promise和其内部的实现,却用草草的一句new Promise()就带过。这一次,借着阅读朴灵大神的《深入浅出Node.Js》,我们自己动手实现一个小小的基本的promise吧。

    构建Promise对象

    首先我们需要回顾一下,一个Promise/A模式和API上是如何定义的:

    • Promise分别有三个状态:pending初始状态,fulfilled完成状态,rejected失败状态。
    • 一旦promise是fulfilled状态或rejected状态,那么它就是不会再改变的。
    • 具备then()方法,用于接收fulfilled和rejected状态的回调方法,并在相应状态下进行触发。
    • then()方法只允许接受function对象,其余的会被忽略。
    • then()方法会返回Promise对象,提供链式调用。
    • then()方法可接收第三个方法,用于支持progress事件的回调方法。

    知道我们的Promise对象需要有什么之后,我们就可以开始尝试写Promise的构造函数了。还有Promise是基于事件机制的,也可以说是发布/订阅模式。所以我们为了演示方便,将使用Node里的events模块。

    还不清楚自定义事件的同学,推荐一个视频给你们入门:阿当大话西游之WEB组件

    var events = require('events'); //events模块
    var util = require('util');     //util工具包模块
    
    var MyPromise = function(){
        events.EventEmitter.call(this); 
    };
    util.inherits(MyPromise, events.EventEmitter); // 继承
    
    MyPromise.prototype.then = function(resolve, reject, progress){
        // this.once()是绑定事件被触发后立即移除事件
        if(typeof resolve === 'function'){
            this.once('success', resolve);
        }
        if(typeof reject === 'function'){
            this.once('error', reject);
        }
        if(typeof progress === 'function'){
            // 不需要once()
            this.on('progress', progress);
        }
        return this;
    };
    

    由此,我们就实现了Promise/A规范。我们用promise对象的then,用相应的事件存放了各个状态的回调函数。那接下来,我们就要知道如何触发这些事件。


    构建Deferred对象

    为了实现事件的触发,我们需要有一个新的对象Deferred。意思是,延迟对象。

    var Deferred = function(){
        this.state = 'pending';
        this.promise = new MyPromise();
    };
    
    Deferred.prototype.resolve = function(obj){
        this.state = 'fulfilled';
        this.promise.emit('success', obj);
    };
    Deferred.prototype.reject = function(err){
        this.state = 'failed';
        this.promise.emit('error', err);
    };
    Deferred.prototype.progress = function(data){
        this.promise.emit('progress', data);
    };
    

    我们可以看到,我们之前定义的promise成为了deferred对象中的一个属性。然后Deferred对象的方法,都是用来触发事件来改变promise状态的。这种模式也称作Promise/Deffered模式,它是基于发布与订阅模式,并提供了更加高级的抽象。Deferred对象,用来控制Promise内部,维护Promise状态。Promise对象,则是作用于外部,通过then(resolve, reject)对外提供接口。

    对于上一篇讲到的promise化的readJSON,我们可以使用我们定义的Promise/Deferred重写一遍:

    var readJSON = function(filename, encoding){
        var deferred = new Deferred();
        fs.readFile(filename, encoding, function(err, res){
            if(err)
                return deferred.reject(err);
            deferred.resolve(JSON.parse(res));
        });
        return deferred.promise;
    };
    
    // 应用
    readJSON('data.json', 'utf-8').then(function(res){
    	console.log(res.message); // Hello World!
    })
    

    对我来说,我更喜欢Promise/Deferred的实现。因为通过Deferred对象,我们可以很随心的控制promise的状态,得到我们想要的样子。当然喜欢原生ES6的那种为Promise构造函数传入工厂函数,也是可以自己改造一下的,和回调参数差不多,可以自行尝试一下。不知道是不是应为我个人水平问题,写起来觉得乱糟糟。所以我更喜欢Promise/Deferred模式的实现。代码如下:

    // 去掉Deferred对象,直接通过回调参数来确定是resolve还是reject。
    var MyPromise = function(factory){
        events.EventEmitter.call(this);
        var _this = this;
        factory && factory(function(res){
            console.log(res);
            _this.emit('success', res);
        }, function(err){
            _this.emit('error', err);
        });
    };
    util.inherits(MyPromise, events.EventEmitter);
    
    // 模拟ES6构造函数方法的应用
    var readJSON = function(filename, encoding){
        return new MyPromise(function(resolve, reject){
            fs.readFile(filename, encoding, function(err, res){
                if(err)
                    return reject(err);
                resolve(JSON.parse(res));
            });
        });
    };
    
    readJSON("data.json", 'utf-8').then(function(data){
        console.log(data.message); // Hello World!
    });
    

    我们通过把json解析后传到resolve()中实现了我们上一篇的readJSON函数promise化的要求!酷~

    开始使用Promise

    从这两篇promise的探究路上,如果能体会到其中的奥妙,应该也差不多可以上道了。这个时候在ES6还未普及前,实现完整优秀promise模式可以借助一些promise库。这里我推荐 Q.js,为了能体现它的高效和优雅,我们借助以往的readJSON例子。

    var Q = require('q');
    var fs = require('fs');
    
    var readFile = function(filename, encoding){
        var deferred = new Q.defer(); // 获取Q的deferred对象
        
        fs.readFile(filename, encoding, function(err, res){
            if(err)
                return deferred.reject(err);
            deferred.resolve(res);
        });
        return deferred.promise;   // 将promise对象return出去,实现链式调用
    };
    
    var readJSON = function(filename, encoding){
        return readFile(filename, encoding).then(JSON.parse);
    };
    
    readJSON('data.json', 'utf-8').then(function(data){
        console.log(data.message); //Hello World!
    });
    

    这里并没有体现优雅,但是可以看到promise/deferred模式的使用。特别是和我一样喜欢这种模式的同学,简直不能再爽!当然,说到优雅,我们可以回想一下,每次我们处理fs.readFile()callback时,我们都是重复的有错误就reject,没错误就resolve。同样的逻辑,其实我们是可以封装起来的,Q就帮我们做到了这一点。

    // 改变fs.readFile()
    var readFile = function(filename, encoding){
        var deferred = new Q.defer(); // 获取Q的deferred对象    
        fs.readFile(filename, encoding, deferred.makeNodeResolver());
        return deferred.promise;   // 将promise对象return出去,实现链式调用
    };
    

    语意可得,弄一个Node式的回调。这个实现其实很简单,书上也有说。这个就交给各位同学们,自己动手试一试吧~

    接下来,会针对我们自己实现的Promise进行拓展,完成我们更多的需求。在此过程中,探究Promise实现原理。为接下来的异步编程学习打下基础。

    再次感谢朴灵老师的《深入浅出Node.Js》。

    再次感谢朴灵老师的《深入浅出Node.Js》。

    再次感谢朴灵老师的《深入浅出Node.Js》。

    重要的话,要说三遍。

  • 相关阅读:
    【WP8】关于类库本地化问题
    【WP8】富文本功能实现
    【WP8】换肤功能的实现
    【WP8】图片缓存控件
    【WP8】图片压缩处理
    【WP8】让TextBox文本支持滑动(Scroll)
    <正则吃饺子> :关于Guava中 Joiner 和 Splitter 的简单使用
    <正则吃饺子> :关于前端往后端传递布尔值参数的问题
    <正则吃饺子>:关于使用powerDesign连接oracle数据库,导出数据表结构(ER图吧)
    <正则吃饺子>:关于集合的简单整理总结
  • 原文地址:https://www.cnblogs.com/YikaJ/p/4468987.html
Copyright © 2011-2022 走看看