QProvider 简介

源码里是这么描述的:

A service that helps you run functions asynchronously, and use their return values (or exceptions) when they are done processing.

大概意思是帮助你异步执行方法,且当他们执行完后可以使用他们的返回值。

This is an implementation of promises/deferred objects inspired by Kris Kowal's Q.

这是一个 promises/deferred 对象的实现,灵感来自于 Kris Kowal's Q

QProvider 用法

下面的例子假设$q和asyncGreet在当前作用域内是有效的.

  1. function asyncGreet(name) {
  2. // perform some asynchronous operation, resolve or reject the promise when appropriate.
  3. return $q(function(resolve, reject) {
  4. setTimeout(function() {
  5. if (okToGreet(name)) {
  6. resolve('Hello, ' + name + '!');
  7. } else {
  8. reject('Greeting ' + name + ' is not allowed.');
  9. }
  10. }, 1000);
  11. });
  12. }
  13. var promise = asyncGreet('Robin Hood');
  14. //then函数放入pending数组
  15. promise.then(function(greeting) {
  16. alert('Success: ' + greeting);
  17. }, function(reason) {
  18. alert('Failed: ' + reason);
  19. });

下面我们深入源码内部去一探究竟:

$QProvider 定义

  1. function $QProvider() {
  2. this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) {
  3. return qFactory(function(callback) {
  4. $rootScope.$evalAsync(callback);
  5. }, $exceptionHandler);
  6. }];
  7. }
  8. $evalAsync: function(expr, locals) {
  9. // if we are outside of an $digest loop and this is the first time we are scheduling async
  10. // task also schedule async auto-flush
  11. //如果当前不处于$digest或者$apply的过程中(只有在$apply和$digest方法中才会设置$$phase这个字段),并且asyncQueue数组中还不存在任务时,
  12. //就会异步调度一轮digest循环来确保asyncQueue数组中的表达式会被执行
  13. if (!$rootScope.$$phase && !asyncQueue.length) {
  14. $browser.defer(function() {
  15. //最终调用的是setTimeout
  16. if (asyncQueue.length) {
  17. $rootScope.$digest();//执行消化功能
  18. }
  19. });
  20. }
  21. asyncQueue.push({scope: this, expression: $parse(expr), locals: locals});
  22. }

由之前 cacheFactory的分析

,再结合上面源码我们就知道 注入$q时调用了qFactory工厂方法:

qFactory

  1. function qFactory(nextTick, exceptionHandler) {
  2. function Promise() {
  3. //初始化Promise的状态对象
  4. this.$$state = { status: 0 };
  5. }
  6. //扩展Promise类原型
  7. extend(Promise.prototype, {
  8. //then主要是把一个defer对象和fullfiled reject 函数 放入pending数组
  9. then: function(onFulfilled, onRejected, progressBack) {
  10. if (isUndefined(onFulfilled) && isUndefined(onRejected) && isUndefined(progressBack)) {
  11. return this;
  12. }
  13. var result = new Deferred();
  14. this.$$state.pending = this.$$state.pending || [];
  15. //把一个新的 Defer 对象push进pending数组
  16. this.$$state.pending.push([result, onFulfilled, onRejected, progressBack]);
  17. if (this.$$state.status > 0) scheduleProcessQueue(this.$$state);
  18. //返回这个新建Defer对象的promise
  19. //可以形成promise chain
  20. return result.promise;
  21. }
  22. //代码省略
  23. }
  24. //通过$q注入时返回Q函数
  25. //随后Q函数 传入resolver参数调用
  26. //调用resolver函数时传入包装deferred对象的resolve和 reject函数
  27. //随后返回promise对象
  28. //promise对象调用then函数时放入pending队列
  29. var $Q = function Q(resolver) {
  30. if (!isFunction(resolver)) {
  31. throw $qMinErr('norslvr', "Expected resolverFn, got '{0}'", resolver);
  32. }
  33. //构造一个Deferred对象
  34. var deferred = new Deferred();
  35. function resolveFn(value) {
  36. deferred.resolve(value);
  37. }
  38. function rejectFn(reason) {
  39. deferred.reject(reason);
  40. }
  41. //调用resolver参数函数
  42. //resolveFn供外部调用
  43. resolver(resolveFn, rejectFn);
  44. //返回一个promise对象
  45. //供调用then函数
  46. return deferred.promise;
  47. };
  48. // Let's make the instanceof operator work for promises, so that
  49. // `new $q(fn) instanceof $q` would evaluate to true.
  50. //使得new $q(fn) 也可以调用promise的方法
  51. $Q.prototype = Promise.prototype;
  52. //暴露内部方法
  53. $Q.defer = defer;
  54. $Q.reject = reject;
  55. $Q.when = when;
  56. $Q.resolve = resolve;
  57. //$q.all是用于执行多个异步任务进行回调,它可以接受一个promise的数组,
  58. //或是promise的hash(object)。任何一个promise失败,都会导致整个任务的失败。
  59. //https://blog.csdn.net/shidaping/article/details/52398925
  60. $Q.all = all;
  61. //$q.race() 是 Angular 里面的一个新方法,和 $q.all() 类似,但是它只会返回第一个处理完成的 Promise 给你///。假定 API 调用 1 和 API 调用 2 同时执行,而 API 调用 2 在 API 调用 1 之前处理完成,那么你就只会得到 //API 调用 2 的返回对象。换句话说,最快(处理完成)的 Promise 会赢得返回对象的机会:
  62. $Q.race = race;
  63. return $Q;
  64. }

调用then方法时实际上是新建一个defer对象放入pending数组,在调用defer.resolve的时候

去调度这个数组中的元素,也就是任务.

resolve 方法

  1. extend(Deferred.prototype, {
  2. resolve: function(val) {
  3. //第一次resolve的时候为0 所以会往下走
  4. if (this.promise.$$state.status) return;
  5. if (val === this.promise) {
  6. this.$$reject($qMinErr(
  7. 'qcycle',
  8. "Expected promise to be resolved with value other than itself '{0}'",
  9. val));
  10. } else {
  11. //调度pending数组里的任务
  12. this.$$resolve(val);
  13. }
  14. },
  15. $$resolve: function(val) {
  16. var then;
  17. var that = this;
  18. var done = false;
  19. try {
  20. if ((isObject(val) || isFunction(val))) then = val && val.then;
  21. if (isFunction(then)) {
  22. //val.then方法 val是promise的时候
  23. //resolvePromise函数里放入了当前defer对象
  24. this.promise.$$state.status = -1;
  25. then.call(val, resolvePromise, rejectPromise, simpleBind(this, this.notify));
  26. } else {
  27. //更新promise的状态对象
  28. this.promise.$$state.value = val;
  29. this.promise.$$state.status = 1;
  30. //调度的时候pending为空就返回了
  31. scheduleProcessQueue(this.promise.$$state);
  32. }
  33. } catch (e) {
  34. rejectPromise(e);
  35. exceptionHandler(e);
  36. }
  37. function resolvePromise(val) {
  38. if (done) return;
  39. done = true;
  40. that.$$resolve(val);
  41. }
  42. function rejectPromise(val) {
  43. if (done) return;
  44. done = true;
  45. that.$$reject(val);
  46. }
  47. }
  48. }

scheduleProcessQueue 方法

  1. //传入promise的state对象
  2. function scheduleProcessQueue(state) {
  3. if (state.processScheduled || !state.pending) return;
  4. state.processScheduled = true;
  5. //nextTick里调用$browser.defer函数
  6. nextTick(function() { processQueue(state); });
  7. }

processQueue 方法

实际最终处理的还是processQueue函数,里面循环调用pending数组

  1. function processQueue(state) {
  2. var fn, deferred, pending;
  3. pending = state.pending;
  4. state.processScheduled = false;
  5. state.pending = undefined;
  6. for (var i = 0, ii = pending.length; i < ii; ++i) {
  7. //获取pending数组的元素 元素本身也是数组
  8. deferred = pending[i][0];
  9. fn = pending[i][state.status];
  10. try {
  11. if (isFunction(fn)) {
  12. deferred.resolve(fn(state.value));
  13. } else if (state.status === 1) {
  14. deferred.resolve(state.value);
  15. } else {
  16. deferred.reject(state.value);
  17. }
  18. } catch (e) {
  19. deferred.reject(e);
  20. exceptionHandler(e);
  21. }
  22. }
  23. }

欢迎关注我的微信公众号,获取最新源码解析文章!

参考资料:

  1. a tiny implementation of Promises/A+.
  2. Angular中的$q的形象解释及深入用法
  3. 关于 Angular 里的 $q 和 Promise
  4. 理解状态机

angular源码剖析之Provider系列--QProvider的更多相关文章

  1. angular源码剖析之Provider系列--CacheFactoryProvider

    CacheFactoryProvider 简介 源码里是这么描述的: Factory that constructs {@link $cacheFactory.Cache Cache} objects ...

  2. angular源码阅读,依赖注入的原理:injector,provider,module之间的关系。

    最开始使用angular的时候,总是觉得它的依赖注入方式非常神奇. 如果你跳槽的时候对新公司说,我曾经使用过angular,那他们肯定会问你angular的依赖注入原理是什么? 这篇博客其实是angu ...

  3. 【安卓网络请求开源框架Volley源码解析系列】定制自己的Request请求及Volley框架源码剖析

    通过前面的学习我们已经掌握了Volley的基本用法,没看过的建议大家先去阅读我的博文[安卓网络请求开源框架Volley源码解析系列]初识Volley及其基本用法.如StringRequest用来请求一 ...

  4. 【java集合框架源码剖析系列】java源码剖析之TreeSet

    本博客将从源码的角度带领大家学习TreeSet相关的知识. 一TreeSet类的定义: public class TreeSet<E> extends AbstractSet<E&g ...

  5. 【java集合框架源码剖析系列】java源码剖析之HashSet

    注:博主java集合框架源码剖析系列的源码全部基于JDK1.8.0版本.本博客将从源码角度带领大家学习关于HashSet的知识. 一HashSet的定义: public class HashSet&l ...

  6. 【java集合框架源码剖析系列】java源码剖析之TreeMap

    注:博主java集合框架源码剖析系列的源码全部基于JDK1.8.0版本.本博客将从源码角度带领大家学习关于TreeMap的知识. 一TreeMap的定义: public class TreeMap&l ...

  7. 【java集合框架源码剖析系列】java源码剖析之ArrayList

    注:博主java集合框架源码剖析系列的源码全部基于JDK1.8.0版本. 本博客将从源码角度带领大家学习关于ArrayList的知识. 一ArrayList类的定义: public class Arr ...

  8. 【java集合框架源码剖析系列】java源码剖析之LinkedList

    注:博主java集合框架源码剖析系列的源码全部基于JDK1.8.0版本. 在实际项目中LinkedList也是使用频率非常高的一种集合,本博客将从源码角度带领大家学习关于LinkedList的知识. ...

  9. 【java集合框架源码剖析系列】java源码剖析之HashMap

    前言:之所以打算写java集合框架源码剖析系列博客是因为自己反思了一下阿里内推一面的失败(估计没过,因为写此博客已距阿里巴巴一面一个星期),当时面试完之后感觉自己回答的挺好的,而且据面试官最后说的这几 ...

随机推荐

  1. jdk与jre安装之后的名字

    jdk与jre安装之后的名字 jdk与jre的区别:https://blog.csdn.net/qq_33642117/article/details/52143824 jdk安装之后的名字: Jav ...

  2. 《C++ Primer Plus》学习笔记9

    <C++ Primer Plus>学习笔记9 第15章 友元.异常和其他 <<<<<<<<<<<<<<& ...

  3. HTML小知识点积累

    1.怎样让heigth:100%起效?   有时候我们设置heigth:100%,想让当前控件铺满整个屏幕,可是非常少情况下这个属性能达到我们想要的效果,这是为什么呢?   而依据W3C的规范.百分比 ...

  4. angularjs中下拉框select option默认值

    1.问题说明: option ng-repeat多空白项 2.解决方案: html: <ion-view hide-nav-bar="true"> <ion-co ...

  5. weex stream 方法封装

    1.封装 api.js // 配置API接口地址 const baseUrl = 'http://www.kuitao8.com/'; // 引入 弹窗组件 var modal = weex.requ ...

  6. SDIO总线(一)

    SDIO接口是在SD内存卡接口的基础上发展起来的接口.SDIO接口兼容曾经的SD内存卡.而且能够连接SDIO接口的设备. SDIO1.0标准定义了两种类型的SDIO卡: 1.全速的SDIO卡.传输率能 ...

  7. 这样看ACM是不是更好?

    如果搞ACM只是为了拿奖,为了保研,这样太功利,整个过程都会变得没意思.我说过我同时看中过程和结果. 其实ACM解题也不是那么没意思,每次AC都有一种非常棒的成就感,每个题目就像是一个解谜游戏,完成了 ...

  8. 利用PHP判断iPhone、iPad、Android、PC设备

    首页那张大图确实是一个比较头疼的问题 在PC上显示是没问题的,可是到手机上就会超出页面一大截,如果做自适应,图片会被强制压缩 无奈只能用wp_is_mobile()函数在手机上隐藏了这张图,可是这函数 ...

  9. HUNNU-10307-最优分解问题

    点击打开题目连接 # include <queue> # include <cstdio> # include <cstring> # include <io ...

  10. Hadoop每日一讨论整理版

    这是我在几个QQ群发起的Hadoop每日一讨论小活动,每天中午2点左右发出一个关于Hadoop的知识片段,在此做一个整理. [每日一讨论]之计算框架(2013-5-21) 就计算框架而言,Hadoop ...