Javascript异步流程控制之Promise(1)-Angular $q简介

@kuitos 2015-08-23 09:40:39发表于 kuitos/kuitos.github.io Angularjavascript

Javascript异步流程控制之Promise(1)-Angular $q简介

原文写于 2015-01-25

先来说说什么是Promise吧

Promise是抽象异步处理对象以及对其进行各种操作的组件。 其详细内容在接下来我们还会学到,Promise并不是从JavaScript中发现的概念。
Promise最初被发现是在 E言語中, 它是基于并列/并行处理设计的一种编程语言。

简言之,Promise就是用于改善异步编程体验的一种编程模型,它提供一系列的api和方法论,让你能更优雅的解决异步编程中出现的一些问题。目前很多第三方框架或类库(如Angular和JQuery)都依照Promise/A+社区制定的规范做了相应的实现(JQuery基于历史原因很多地方与Promise规范不一致,so不建议通过JQuery源码学习Promise),最主要的是,Promise现在已经成为ES6的既定标准,目前部分高版本浏览器已原生支持Promise(后面有机会给出demo),所以我们还是很有必要来了解一下这到底是一个什么东西。

首先来看看,Promise的核心竞争力在哪

以前我们在处理一系列有依赖性的回调的时候,我们的代码是这样写的

step1(function (value1) {
    step2(value1, function(value2) {
        step3(value2, function(value3) {
            step4(value3, function(value4) {
                // Do something with value4
            });
        });
    });
});

是的,就是一层层的回调嵌套,传说中的回调地狱
那么如果我们换成Promise的方式来实现呢

step1().then(step2).then(step3).then(step4)

效果显而易见,代码简单逻辑清晰,异步的回调嵌套写法变成了同步(本质上当然还是异步的)的写法看上去是不是优雅多了

目前,Angular基于现在流行的NodeJs异步流程控制库Q实现了一个微缩版的Q,它提供了一些最常用的规范的Promise API,并对外提供了$q这样一个service,这里我们介绍一下angular框架中主要有哪些api及相应的使用场景。(声明一点,angular中所有的ajax请求均返回promise)

  1. Promise.then() 将回调嵌套变成链式调用。then可以接两个参数,sucessCallback 和 errorCallback,即then(successCb, errorCb)
  2. Deferred.resolve(val) 通知promise请求处理完毕,并将处理结果传给回调函数
  3. Deferred.reject(msg) 通知promise请求出现异常,将异常信息传给回调函数
  4. $q.when(val/fn) 将任意 对象/函数 包装成promise,返回包装好的promise
  5. $q.all(promises).then() 当所有promise都成功解析后流程才继续往下走
    使用场景

Promise.then()

// 通常,我们处理多层顺序依赖的异步调用,我们会这样去写
$http.get().success(function (val1){
    $http.get(val1).success(function (val2){
        $http.get(val2).success(function (val3){
            console.log(val3);
        })
    });
});

// 当用Promise来处理时,写法会变成这样
function funcA(val1){
    return $http.get(val1).suceess(function (val2){
        return val2;   
    })
}

function funcB(val2){
    return $http.get(val2).suceess(function (val3){
        return val3;   
    })
}

function funcC(val3){
    console.log(val3);
}
$http.get().then(funcA).then(funcB).then(funcC);

// 很显然,使用了Promise方式代码可读性变的强很多

Deferred.resolve

// Deferred.resolve用于通知promise结果已经处理好,可以开始处理回调了
// 假设我们有这样一个业务,按钮点击时的处理逻辑依赖于另一个函数异步返回的数据,就像这样
var a;
setTimeout(function(){
    a = 10;
},5000);
dom.onclick = function(){
    console.log(a);
}

// 我们总不能在onclick里轮询直到a被赋值吧。。
// 有了Promise一切变得简单
var defer = $q.defer(),
    a;
setTimeout(function (){
    defer.resolve(10);
},5000);
dom.onclick = function(){
    defer.promise.then(function(a){
        console.log(a);
    });
}

Deferred.reject

// 用法同deferred.resolve,只不过它调用之后会走失败回调
setTimeout(function(){
    defer.reject(10);
},5000);
defer.promise.then(function successCb(a){
    console.log(a+"success");
}, function errorCb(a){
    console.log(a+"error");
});

// 5秒后打出 "10error"

Promise.all

// 这个api就非常给力了,假设我们有这样一个场景
// 页面上有A、B、C、D四块区域,其中A、B、C三块数据都是ajax获取的,D展示的数据需要综合A、B、C三个的数据
// 难道我们得在 A 的请求回调里调用 B ,然后再B请求回调里调C这样一层层嵌套,直到所有请求准备好了再去处理D ?这样整个页面在同一时间只能有一个请求发出,效率太低
// 可以这样写

$q.all([promiseA,promiseB,promiseC]).then(funcD)

// 这样页面在同一时间会发出三个请求,当所有请求都好了之后再去处理D页面,代码不仅变得更清晰而且效率更高
最后介绍一下Promise.race([promises]),这个api angular并没有做实现,但是它已列入Promise/A+规范中,这里提一下
Promise.race([promises])与Promise.all([promises])类似,只不过Promise.all是与集运算,而Promise.race()是或集运算,当promises中有一个被resolve了就会继续后面的then

Promise.race

// 当promiseA或promiseB其中有一个被resolve,则后面funcT会被执行
// 使用场景有:进入一个页面时 当用户点击某个按钮或过5s 则展示某个提示,使用这个api会很方便
Promise.race([promiseA,promiseB]).then(funcT);