在 AngularJS 服务中缓存一个承诺对象

Posted

技术标签:

【中文标题】在 AngularJS 服务中缓存一个承诺对象【英文标题】:Caching a promise object in AngularJS service 【发布时间】:2013-09-15 16:37:21 【问题描述】:

我想使用 Promises 在 AngularJS 中实现静态资源的动态加载。问题:我在页面上有几个组件可能(或不,取决于显示的,因此是动态的)需要从服务器获取静态资源。加载后,可以在整个应用程序生命周期内进行缓存。

我已经实现了这个机制,但是我是 Angular 和 Promises 的新手,我想确定这是否是一个正确的解决方案\方法。

var data = null;
var deferredLoadData = null;

function loadDataPromise() 
  if (deferredLoadData !== null)
    return deferredLoadData.promise;

  deferredLoadData = $q.defer();

  $http.get("data.json").then(function (res) 
    data = res.data;
    return deferredLoadData.resolve();
  , function (res) 
    return deferredLoadData.reject();
  );

  return deferredLoadData.promise;

因此,只发出了一个请求,接下来对 loadDataPromise() 的所有调用都会取回第一个做出的承诺。它似乎适用于正在进行中或前一段时间已经完成的请求。

但是缓存 Promises 是不是一个好的解决方案?

【问题讨论】:

此行不正确:return deferredLoadData.resolve();。您应该只是 deferredLoadData.resolve();deferredLoadData.reject();return 语句使整个承诺返回 undefined @arcol - 你是对的。这没有意义。事实上,它什么也没做。 (但它也不会破坏任何东西,因为没有使用链) 【参考方案1】:

这种设计模式将缓存第一次运行时返回的内容,并在每次再次调用时返回缓存内容。

const asyncTask = (cache => 
  return function()
    // when called first time, put the promise in the "cache" variable
    if( !cache )
        cache = new Promise(function(resolve, reject)
            setTimeout(() => 
                resolve('foo');
            , 2000);
        );
    
    return cache;
  
)();

asyncTask().then(console.log);
asyncTask().then(console.log);

解释:

简单地用另一个返回函数的自调用函数(你原来的异步函数)包装你的函数,包装函数的目的是为局部变量cache提供封装范围,以便局部变量只能访问在包装函数的返回函数中,并且每次调用 asyncTask 时具有完全相同的值(第一次除外)

【讨论】:

【参考方案2】:

对于这个任务,我创建了名为 defer-cache-service 的服务,它删除了所有这些样板代码。它是用 Typescript 编写的,但你可以获取编译后的 js 文件。 Github source code.

例子:

function loadCached() 
   return deferCacheService.getDeferred('cacke.key1', function () 
      return $http.get("data.json");
   ); 
 

消费

loadCached().then(function(data) 
//...
);

需要注意的重要一点是,如果假设两个或多个部分同时调用相同的 loadDataPromise,则必须添加此检查

if (defer && defer.promise.$$state.status === 0) 
   return defer.promise;

否则您将重复调用后端。

【讨论】:

【参考方案3】:

这是正确的方法吗?

是的。在返回的函数上使用memoisation 保证了一种通用技术,可以避免重复执行异步(通常是昂贵的)任务。 Promise 使缓存变得容易,因为不需要区分正在进行的操作和已完成的操作,它们都表示为(相同的)结果值的 Promise。

这是正确的解决方案吗?

没有。全局data 变量和undefined 的分辨率并不是promise 的预期工作方式。相反,用结果data 来履行承诺!它还使编码变得更加容易:

var dataPromise = null;

function getData() 
    if (dataPromise == null)
        dataPromise = $http.get("data.json").then(function (res) 
           return res.data;
        );
    return dataPromise;

那么,不是loadDataPromise().then(function() /* use global */ data ),而是简单的getData().then(function(data) … )

为了进一步改进该模式,您可能希望在闭包范围内隐藏dataPromise,并注意当getData 采用参数(如url)时,您需要查找不同的promise。

【讨论】:

为什么不使用 $cacheFactory?这不是更灵活地从整个模块访问缓存吗? @Darryl:抱歉,我对 Angular 了解不多,我的回答仅基于 Promise。 @Darryl:我想主要的区别是通过使用 $cacheFactory 缓存服务调用的结果,通过“dataPromise”缓存承诺本身。第二种方式,如果你调用了两次服务,第一次调用还没有返回,第二次调用不会被触发,当第一次调用返回时,两个promise都被解析了。在某些情况下,这是一个很大的优势 你最终为我节省了很多冗余和错误的代码

以上是关于在 AngularJS 服务中缓存一个承诺对象的主要内容,如果未能解决你的问题,请参考以下文章

AngularJS 中的链式承诺

如何模拟在 AngularJS Jasmine 单元测试中返回承诺的服务?

AngularJS 承诺处理 CoffeeScript 类

AngularJS 测试 - 来自服务的承诺无法解决

拒绝承诺时AngularJS服务重试

使用 AngularJS 立即返回一个已解决的承诺