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

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

异步编程解决方案

Promise/Deferred模式

使用事件的方式时,执行流程都需要预先设定好,并且包括相关分支的逻辑,这是由于发布/订阅模式决定的。

$.get('/api',{
    success: onSuccess,
    error: onError,
    complete: onComplete
})

上面的方式,必须要提前设计好如何处理目标。但是如果使用Promise/Deferred模式,则可以实现先执行异步调用,延迟传递处理。

Promise/Deferred模式被JQuery1.5中广为人知。Ajax经过重写后,调用Ajax可以通过如下调用。

$.get('/api')
  .success(onSuccess)
  .error(onError)
  .complete(onComplete)

这个方式比预先传入一个对象的方式更加灵活,并且不调用success()error(),complete(),请求也会照常调用。而且原先一个事件只能处理一个回调,但是如下所示,可以对事件加入任意多个业务逻辑。

$.get('/api/)
  .success(onSuccess1)
  .success(onSuccess2);

Promise/Deferred在2009年被抽象Promise/A,Promise/B,Promise/D 这样典型的Promise/Deferred 异步模型作为草案发布在CommonJs规范中。使得异步调用更加优雅。

异步的广度使得回调的增加,一旦回调增加,编程体验将会变的很不愉快,但是Promise/Deferred 模式在一定程度上缓解了这个问题。

Promise/A

Promise/A提议对单个异步操作做出如下定义。

  • Promise只会存在以下三个状态:未完成态,完成态和失败态。
  • Promise的状态只会出现从未完成态转向完成态或失败态。不能逆转,完成态和失败态不能相互转化。
  • Promise的状态一旦改变,将不能改变。

1

Promise/A 模式的API的定义比较简单,Promise 对象存在then()方法即可。不过then()方法有如下要求。

  • 接受完成态或错误态的回调方法,当操作完成时或者错误时,调用相应的方法。
  • 可选的支持progress事件回调作为第三个方法。
  • then()只接受function对象,其他对象将会忽略。
  • then()返回Promise,支持链式调用。

定义如下 then(fulfilledHandler,errorHandler,progressHandler)

var Promise = function(){
    EventEmitter.call(this);
}
util.inherits(Promise,EventEmitter);

Promise.prototype.then = function(fulfillHandler,errorHandler,progressHandler){
    if(typeof fulfilledHandler === 'function'){
        this.once('success',fulfilledHandler);
    }
    if(typeof errorHandler === 'function'){
        this.once('error',errorHandler);
    }
    if(typeof progressHandler === 'function'){
        this.on('progress',progressHandler);
    }
    return this;
};

then()方法所做的工作就是存放这些回调函数。为了完成整个流程,还需要触发这些回调,我们把这些对象称为Deferred,即延时对象。

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

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);
}

这里的状态和方法的关系如图所示: 2

我们可以根据典型的响应对象进行封装。

res.setEncoding('utf8');
res.on('data',function(chunk){
    console.log('BODY:' + chunk);
});
res.on('end',function(){
    // Done
})
res.on('error',function(err){
    // Error
})

转换如下

res.then(function(){
    //Done
},function(err){
    //Error
},function(chunk){
    console.log('BODY:' + chunk);
})

改造代码如下

var promisify = function(res){
    var deferred = new Deferred();
    var result = '';
    res.on('data',function(chunk){
        result += chunk;
        deferred.progress(chunk);
    });
    res.on('end',function(){
        deferred.resolve(result);
    });
    res.on('error',function(err){
        deferred.reject(err);
    });
    return deferred.promise;
}

如此便得到了简单的结果,返回promise 的目的是为了不让外部使用reject 和resolve。更改内部状态由定义者处理。

promisify(res).then(function(){
    // Done
},function (err){
    // Error
},function (chunk){
    // progress
    console.log('BODY:' + chunk);
})

Deferred主要是用于处理内部状态变化,维护异步模型的状态。Promise用于外部,通过then()方法暴露给外部添加自定义方法。

Promise 和Deferred的整体关系如图所示。

3

与事件订阅/发布模式相比,Promise/Deferred 模式的API接口和抽象模型变得更加灵活和简单。将业务中不变的封装在Deferred中,变化的封装在Promise中,在不同的场景下封装和改造其Deferred,当场景不常用,则需要好好取舍封装的成本和带来的简介的关系了。

Promise是高级接口,事件是低级接口。低级接口可以构造更加复杂的业务场景,高级接口一旦定义不容易变化,但是对于解决复杂问题更加有效。

Promise中多异步协作

介绍Promise提到过,主要是用于解决单个异步操作时的问题,但是当多个异步操作时该怎么处理。

类似EventProxy,这里给出了相对简单的原型实现。

Deferred.prototype.all = function(promises){
    var count = promises.length;
    var that = this;
    var results = [];
    promises.forEach(function(promise,i){
        promise.then(function(data){
            count--;
            results[i] = data;
            if(count === 0){
                that.resole(results);
            }
        },function(err){
            that.reject(err);
        });
    });
    return this.promise;
}

如下,读取多个文件时,all 将多个Promise 重新抽象组合为一个Promise。

var promise1 = readFile("foo.txt","utf-8");
var promise2 = readFile("bar.txt","utf-8");
var deferred = new Deferred();
deferred.all([promise1,promise2]).then(function(results){
    // TODO
},function(err){
    // TODO
});

这里all()方法抽象为多个异步操作,只有当所有的异步操作成功时,才算成功,如果一个异步操作失败,整个异步操作就算失败。

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

回到顶部