异步编程总结-5
发布于 6 年前 作者 halu886 3210 次浏览 来自 分享

该文章阅读需要5分钟,更多文章请点击本人博客halu886

Promise的进阶知识

在API的暴露上,Promise比原生的事件侦听和触发更加优雅,但是原始的方式更加灵活,但是在一些经典的场景下,封装API并不高,还是值得一做。

Promise的秘诀在于队列的操作,当存在需要和服务器进行多次指令操作时,可以用deferred模式解决问题。

当有一组纯异步的API,操作如下

obj.api1(function(value1){
    obj.api2(function(value2){
        obj.api3(function(value3){
            obj.api4(function(value4){
                callback(value4)
            }
        }
    }
})

当每个请求都依赖于上一个请求时,当业务一复杂,嵌套则非常不友好,也被称为“Pyramid of Doom”。也被译为回调金字塔。

如果尝试用普通函数进行展开

var handle1 = function (value1){
    obj.api2(value1,handler2);
}

var handle2 = function (value2){
    obj.api3(value2,handler3);
}

var handle3 = function (value3){
    obj.api4(value3,handler4);
}

var handle4 = function (value4){
    callback(value4);
}

obj.api1(handler1);

如果使用事件进行展开

var emitter = new event.Emitter();

emitter.on("step1",function(){
    obj.api1(function(value1){
        emitter.emit("step2",value1);
    })
});

emitter.on("step2",function(value1){
    obj.api2(value1,function(value2){
        emitter.emit("step3",value2);
    })
});

emitter.on("step3",function(value2){
    obj.api3(value2,function(value3){
        emitter.emit("step4",value3);
    })
});

emitter.on("step4",function(value3){
    obj.api4(value3,function(value4){
        callback(value4);
    })
});

emitter.emit("step1");

通过事件模式进行展开越来越糟糕了,代码行数变多了。

支持序列执行的Promise

理想的编程方式应该时前一个调用完后发起下一个调用,也就是所谓的链式调用。

promise()
    .then(obj.api1)
    .then(obj.api2)
    .then(obj.api3)
    .then(obj.api4)
    .then(function(value4){
        // Do something with value4
    },function(error){
        // Handle any error from step1 through step3
    })
    .done();

尝试改造一下,调用如下

var Deferred = function(){
    this.promise = new Promise();
}

Deferred.prototype.resolve = function(obj){
    var promise= this.promise;
    var handler;
    while((handler = promise.queue.shift())){
        if(handler && handler.fulfilled){
            var ret = handler.fulfilled(obj);
            if(ret && ret.isPromise){
                ret.queue = promise.queue;
                this.promise = ret;
                return;
            }
        }
    }
}

// 失败态
Deferred.prototype.reject = function(err){
    var promise = this.promise;
    var handler;
    while((handler = promise.queue.shift())){
        if(handler && handler.error){
            var ret = handler.error(err);
            if(ret && ret.isPromise){
                ret.queue = promise.queue;
                this.promise = ret;
                return;
            }
        }
    }
};

// 生成回调函数
Deferred.prototype.callback = function(){
    var that = this;
    return function(err,file){
        if(err){
            return that.reject(err);
        }
        that.resolve(file);
    };
};

var Promise = function(){
    // 队列用于存储待执行的回调函数
    this.queue = [];
    this.isPromise = true;
}

Promise.prototype.then = function(fulfilledHandler,errorHandler,progressHandler){
    var handler = {};
    if(typeof fulfilledHandler === 'function'){
        handler.fulfilled = fulfilledHandler;
    }
    if(typeof errorHander === 'function'){
        handler.error = errorHandler;
    }
    this.queue.push(handler);
    return this;
}

这里我们以读取两次文件为例,第二个文件的读取依赖于第一个文件的内容。

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

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

readFile1('file1.txt','utf8').then(function(file1){
    return readFile2(file1.trim().'utf8');
}).then(function(file2){
    console.log(file2);
})

让Promise支持链式,主要有两个步骤:

  1. 让所有回调都存在队列中
  2. Promise完成,逐个执行回调,当返回Promise时,当前Deferred对象的 promise引用指向新的 Promise对象。且余下的回调转交给它。

将API Promise化

这里提供一个方法可以实现 API批量 Promise化

// smooth(fs.readFile);
var smooth = function(method){
    return function(){
        var deferred = new Deferred();
        var args = Array.prototype.slice.call(arguments,1);
        args.push(deferred.callback());
        method.apply(null,args);
        return deferred.promise;
    };
};

于是前两次文件的构造如下

var readFile1 = function(file,encoding){
    var deferred = new Deferred();
    fs.readFile(file,encoding,deferred.callback());
    return deferred.promise;
}
var readFile2 = function(file,encoding){
    var deferred = new Deferred();
    fs.readFile(file,encoding,deferred.callback());
    return deferred.promise;
}

可以简化为

var readFile = smooth(fs.readFile);

实现同样的效果时,代码量将会锐减到到

var readFile = smooth(fs.readFile);
readFile('file1.txt','utf8').then(function(file1){
    return readFile(file1.trim(),'utf8');
}).then(function(file2){
    console.log(file2);
})

以上知识点均来自<<深入浅出Node.js>>,更多细节建议阅读书籍:-)

回到顶部