模拟Promise_Node异步流程控制3

写在前面

铺天盖地的Promise文章还没完全消散,Promise本身就被ES7劝退了

关于Promise的具体语法及应用场景,可以查看:

至于另一篇(动手实现promise),不建议看,这个实现存在的问题很多(性能、未知bug等等),也不打算继续维护,千万不要用

Promise作为一种异步流程控制方法,本篇介绍其实现机制,并模拟实现基本功能

一.基础部分

Promise内部分为Promise对象与Deferred对象,前者提供接口接收handler,后者记录状态,维持异步逻辑正确执行(男主外女主内的感觉promise.then()接收handler,内部的deferred负责维护状态)

Promise

Promise主外,接收handler并注册给事件机制

简单情况下,Promise可以直接套用EventEmitter,如下:

var EventEmitter = require('events');
var util = require('util');

//--- 自定义promise
var MyPromise = function() {
    EventEmitter.call(this);
};
util.inherits(MyPromise, EventEmitter);

然后公开then(),负责接收handler:

MyPromise.prototype.then = function(onFulfilled, onRejected, onProgress) {
    if (typeof onFulfilled === 'function') {
        this.once('resolve', onFulfilled);
    }
    if (typeof onRejected === 'function') {
        this.once('reject', onRejected);
    }
    if (typeof onProgress === 'function') {
        this.on('progress', onProgress);
    }
};

主外的就结束了,完全看不到将要成为Promise的端倪(现在确实不像,因为这个Promise不是最终暴露出去的Promise)

Deferred

Deferred要维护状态,稍复杂一些

初始状态,如下:

var Deferred = function() {
    this.state = 'pending';
    this.promise = new MyPromise();
};

Deferred实例持有promise引用以便触发事件执行handler(主内的管着主外的)

然后把事件机制与状态联系起来:

Deferred.prototype.resolve = function(res) {
    this.promise.emit('resolve', res);
    this.state = 'resolved';
};
Deferred.prototype.reject = function(err) {
    this.promise.emit('reject', err);
    this.state = 'rejected';
};
Deferred.prototype.progress = function(chunk) {
    this.promise.emit('progress', chunk);
};

最基础的部分完成了,最后提供一个Promise并暴露出去:

module.exports = {
    Promise: MyPromise,
    Deferred: Deferred,
    MyPromise: function(fn) {
        // 向fn注入reslove和reject
        var deferred = new Deferred();
        fn.call(deferred, (res) => {
            process.nextTick(() => {deferred.resolve(res);});
        }, (err) => {
            process.nextTick(() => {deferred.reject(err);});
        });
        return deferred.promise;
    }
};

module.exports.MyPromise就是最终产物,延迟执行reslove/reject是为了等待外部的promise.then(),也就是先让handler同步注册,然后再触发事件,这也是Promise不用预先指定分支及延迟逻辑处理的秘密所在

示例如下:

var P = require('./p.js');

var p1 = new P.MyPromise(function(resolve, reject) {
    console.log('#1');
    resolve(1);
    console.log('#2');
});
p1.then((res) => {console.log(res);});
// log print:
// #1
// #2
// 1

var p2 = new P.MyPromise(function(resolve, reject) {
    console.log('#1');
    reject(new Error('reject'));
    console.log('#2');
});
p2.then(null, (err) => {console.log(err.toString());});
// log print:
// #1
// #2
// Error: reject

二.扩展功能

1.回调函数生成

Node回调函数大都遵循callback(err, res1, res...)的规则,可以据此生成回调函数:

// node callback
Deferred.prototype.callback = function() {
    return (err, value) => {
        if (err) {
            this.reject(err);
        }
        else if (arguments.length > 2) {
            this.resolve(Array.prototype.slice.call(arguments, 1));
        }
        else {
            this.resolve(value);
        }
    };
};

可以简化promisify过程,让Promise更好用一些

2.多依赖异步控制

各个异步库都提供了依赖控制方法,比如大而全的async模块提供的series(), parallel(), waterfall()等等

Promise提供的依赖控制不够多,只支持all(), race(),这里模拟实现all(),如下:

// 多依赖异步控制
Deferred.prototype.all = function(promises) {
    var results = [];
    var count = promises.length;

    promises.forEach((promise, i) => {
        promise.then((data) => {
            results[i] = data;
            count--;
            if (count === 0) {
                this.resolve(results);
            }
        }, (err) => {
            this.reject(err);
        });
    });

    return this.promise;
};

用法示例:

// 多依赖异步控制
var fs = require('fs');

// promisify
var readFile = function(file, encoding) {
    encoding = encoding || 'utf-8';

    var deferred = new P.Deferred();
    fs.readFile(file, encoding, deferred.callback());
    return deferred.promise;
};
// test
var p1 = readFile('./p.js');
var p2 = readFile('./index.js');
var d1 = new P.Deferred();
d1.all([p1, p2]).then((arrRes) => {
    var aRes = arrRes.map((res) => {
        return res.toString().slice(0, 20);
    });
    console.log(aRes);
}, (err) => {
    console.log(err);
});
// log print:
// [ 'var EventEmitter = r', 'var P = require(\'./p' ]

这里没有把Deferred包装起来,更清晰一些(其实是懒得包装了- -)

3.Promise链

支持Promise链需要维护一个队列,并根据队列中每个promise的状态手动控制,偷懒的事件机制不再适用了(嗯,需要大改)

Promise

剔除事件机制,采用任务队列

//--- 自定义promise
var MyPromise = function() {
    // 用于支持promise链
    this.queue = [];
    this.isPromise = true;
};

MyPromise.prototype.then = function(onFulfilled, onRejected) {
    // 采用任务队列手动控制,不利用事件机制触发执行了
    var handler = {};

    if (typeof onFulfilled === 'function') {
        handler.onFulfilled = onFulfilled;
    }
    if (typeof onRejected === 'function') {
        handler.onRejected = onRejected;
    }
    this.queue.push(handler);
    return this;
};

Deferred

手动执行回调,并传递结果

var Deferred = function() {
    this.state = 'pending';
    this.promise = new MyPromise();
};
Deferred.prototype.resolve = function(res) {
    var handler;

    while (handler = this.promise.queue.shift()) {
        if (handler && handler.onFulfilled) {
            // 执行肯定回调
            var ret = handler.onFulfilled(res);
            // 如果肯定回调的返回值为promise,更新this.promise
            if (ret && ret.isPromise) {
                ret.queue = this.promise.queue;
                this.promise = ret;
                return;
            }
        }
    }
};
Deferred.prototype.reject = function(err) {
    var handler;

    while (handler = this.promise.queue.shift()) {
        if (handler && handler.onRejected) {
            var ret = handler.onRejected(err);
            if (ret && ret.isPromise) {
                ret.queue = this.promise.queue;
                this.promise = ret;
                return;
            }
            else {
                // 把ret传入下一个handler的onFulfilled
                var nextHandler = this.promise.queue.shift();
                if (nextHandler && nextHandler.onFulfilled) {
                    nextHandler.onFulfilled(ret);
                }
                return;
            }
        }
    }
};

用法示例如下:

// promise链
var NewP = require('./newp.js');

// promisify
var readFile = function(file, encoding) {
    encoding = encoding || 'utf-8';

    var deferred = new NewP.Deferred();
    fs.readFile(file, encoding, deferred.callback());
    return deferred.promise;
};

// test resolve
readFile('./p.js').then((data) => {
    return readFile('./index.js');
}).then((data) => {
    console.log('index: ' + data.slice(0, 20));
});
// log print:
// index: var P = require('./p

// test reject
readFile('./a.bcd').then((data) => {
    return readFile('./p.js');
}, (err) => {
    console.log('!!!error occurs: ' + err);
    return 'something for next onFulfilled';
}).then((data) => {
    console.log(data);
});
// log print:
// !!!error occurs: Error: ENOENT: no such file or directory, open...
// something for next onFulfilled

4.smooth

smooth表示promisify现有API,技巧是篡改arguments,如下:

var smooth = (method) => {
    return function() {
        var deferred = new P.Deferred();
        var args = Array.prototype.slice.call(arguments, 0);
        args.push(deferred.callback());
        method.apply(null, args);
        return deferred.promise;
    };
};

由smooth内部偷偷提供callback,用法很简洁:

var readFile = smooth(fs.readFile);
readFile('./index.js', 'utf-8').then((res) => {
    console.log(res.slice(0, 20));
});

异步方法的回调函数从代码中消失了。这个技巧对于“正版Promise”同样适用,需要用Promise包装大量API时可以考虑

三.总结

Promise在舞台上转了一圈,然后没能登上领奖台

作为异步流程控制方案,Promise最大的问题就是存在封装(promisify)成本,而且提供的多依赖控制方法不够用,需要自行扩展,所以,不好用,不好用就不会长久

过时了就没什么好说的,还记得当时铺天盖地的《关于Promise你不知道的…》、《你真的会用Promise吗》

参考资料

  • 《深入浅出NodeJS》

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*

code