浅析 Angular $q promise 回调异步嵌套解决机制

来源:互联网 发布:软件测试名词解释 编辑:程序博客网 时间:2024/04/30 20:43

前言

大概一个多个月忙着做公司开发的项目,做这个项目以来有些心得,但是感觉还是不如闷头学来的快,毕竟是实战,在这一个月以来,经历过各种适应布局,还算小有收获,不过最大的遗憾是荒废了angular的学习进度,所以趁着闲季,小补一下Angular ,也算对自己的一个巩固。

一 、Promise的出现

随着node.js的出现,异步逐渐被普及到web程序中来,随着异步带来的好处,也伴随着“回调噩梦”的出现,比如形如下面的代码我们称作为“回调噩梦”或者是“回调金字塔”

fs.readFile("../data.txt",(err,data) => {    fs.readFile("../"+data+".txt",(err,data) => {        fs.readFile("../"+data+".txt",(err,data) => {            ...        });    });});

回调嵌套出现的主要原因来自于下一个异步操作需要上一个异步操作的结果。
上面的例子,每个读取文件的操作都把上一次的结果当做路径来进行读取

真正出现这些状况的来源是将要执行的异步操作互相依赖,比如说一个连接Mongodb的例子。

每一个对mongodb的DURD操作得到的结果都会当做一个回调函数的参数返回,连同db的实例一起,因此,你如果要操作db,你始终得嵌套着回调来执行,当然,这个是有其他的解决方案的,今天我们主要看一下Promise。

看了上面的例子,多个回调函数的嵌套会带来很多问题,诸如可读性差代码冗余等。

为了解决这个问题,在es7草案中出现的async就是一个非常好的解决方案,他是generator函数的语法糖。
在它之前也有过一个关于回调嵌套的解决方案,它就是Promise。

Promise并没有从根本解决回调嵌套的问题,它带来的是把原先横向发展的代码变为流式的纵向结构。

比如上面的例子,我现在用promise来做。

let readFile = path => {  return new Promise((resolve,rejected) => {    fs.readFile(path,(err,data) => {      if(err) rejected(err);      else resolve(data);    });  });}readFile("../data.txt").then(data => {  readFile("../"+data+".txt");}).then(data => {  readFile("../"+data+".txt");}).then(data => {  readFile("../"+data+".txt");}).catch(err) => {  console.log("there was a error : " + err);};

Promise很显然的解决了代码的可读性的问题。不得不说也是现在很流行的一种解决方案。

Promise对象存在于es6的标准中。然而在Angular中也封装了一个Promise,以服务的形式存在。

二 、$q

qAngularPromiseq.defer()来返回一个promise实例。
在Angular源码中的q.js 302行 :

function defer() {    return new Deferred();  }

这里可以看出defer方法是返回了一个Deferred对象,先别急,我们再来看看Deferred的构造函数。

306行:

function Deferred() {    var promise = this.promise = new Promise();    //Non prototype methods necessary to support unbound execution :/    this.resolve = function(val) { resolvePromise(promise, val); };    this.reject = function(reason) { rejectPromise(promise, reason); };    this.notify = function(progress) { notifyPromise(promise, progress); };  }

这里是返回了一个Promise对象,并把它赋值给了实例的promise属性和一个局部变量

下面做了三次赋值, resolve,rejected,notify实际上是声明了三个方法。
这里不得不得提,Promise对象有三个状态,分别是Pending(进行中),resolve(解决),rejected(未解决)。

其中pending 是正在执行的状态,pending状态可以转换为resolve状态,也可以转换为rejected状态。但是状态一旦发生改变也就不能再次变更了,换种说法,rejected状态和rejected状态都不能发生状态转换。

deffered对象中的resolve 和 rejected 对应于promise中的resolve和rejected状态,notify是用来进行状态更新通知的。

下面看个例子。
setTimeout是一个异步函数,第一个参数传入需要执行异步操作的函数。
下面我贴上部分代码
html部分

<button class="btn btn-success" ng-click='test()'> 测试$q</button>

js部分

let testFn = () => {                        let deffered = $q.defer();                        setTimeout(() => {                            deffered.reject("err");                        },1000);                        return deffered.promise;                    }                    $scope.test = () => {                        testFn()                            .then(data => {                                console.log(data);                            })                            .catch(err => {                                console.log(err);                            })                    }

按下测试按钮。
output

err

这里我们直接转到了rejected状态。
值得注意的是,如果reject方法和resolve方法都返回一个promise对象,那么,我们可以链式调用。

我们改写了一下上面那个例子

let testFn = () => {                        let deffered = $q.defer();                        setTimeout(() => {                            deffered.resolve("data");                        },1000);                        return deffered.promise;                    }                    $scope.test = () => {                        testFn()                            .then((data) => {                                console.log(data);                                return testFn();                            })                            .then((data) => {                                console.log(data);                                return testFn();                            })                            .then((data) => {                                console.log(data);                                return testFn();                            })                            .catch(err => {                                console.log(err);                            })                    }

点击按钮之后可以看到,每过一秒打印出了一个 “data”
output:

datadatadata

必须注意的是,因为整个操作都在promise 的作用域内,如果我们不捕获reject的错误的话,是不会当做异常进行抛出的。

原创粉丝点击