利用promise实现简单的前端cache

今天在工作中遇到一个关于promise有趣的小问题,这里分享一下分析的过程。

原始版本

//这个方法模拟从服务端加载数据
var loadData = function(){
  return fetch('/').then(function(data){
    return data.statusText
  });
};

loadData().then(function(data){
  console.log(data);
});

上面这一小段方法本也没什么错,但考虑如果使用数据的地方比较多,每个地方都向服务端加载数据,这样会不会加重服务端压力?

来个简单的缓存

你一定会说来个简单的缓存吧,如下所示:

//定义一个变量充当缓存
var cache = null;

//下面的方法使用了cache
var loadData = function(){
  if(cache === null) {
    return fetch('/').then(function(data){
      cache = data.statusText;
      return cache;
    });
  } else {
    return Promise.resolve(cache);
  }
};

//再定义了一个重新加载数据的方法
var reloadData = function(){
  cache = null;
  return loadData();
};

loadData().then(function(data){
  console.log(data);
});

一眼看过去,好像没有什么问题。

但经过仔细推敲代码,发现还是存在问题的。当调用两次loadData()方法,而在调用第二次方法时,cache还为null,因此最终还是fetch了两次。

判断一下promise的状态

你一定会说要判断一下promise的状态,好吧,这样试一下。

var loadPromise = null;

var loadData = function(){
  //在加载数据时,如发现loadPromise为null,才重新加载
  if(loadPromise === null) {
    loadPromise = fetch('/').then(function(data){
      return data.statusText;
    });
  }
  //否则返回已经存在的promise对象
  return loadPromise;
};

var reloadData = function(){
  loadPromise = null;
  return loadData();
};

loadData().then(function(data){
  console.log(data);
});

可以看到上述代码连cache变量都没使用了。这里是将loadPromiseresolved值当成缓存来用了。

为啥可以这么干?参见这里https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise

Promise 对象是一个返回值的代理,这个返回值在promise对象创建时未必已知。它允许你为异步操作的成功或失败指定处理方法。 这使得异步方法可以像同步方法那样返回值:异步方法会返回一个包含了原返回值的 promise 对象来替代原返回值。

Promise对象有以下几种状态:

  • pending: 初始状态, 既不是 fulfilled 也不是 rejected.
  • fulfilled: 成功的操作.
  • rejected: 失败的操作.

pending状态的promise对象既可转换为带着一个成功值的fulfilled 状态,也可变为带着一个失败信息的 rejected 状态。当状态发生转换时,promise.then绑定的方法(函数句柄)就会被调用。(当绑定方法时,如果 promise对象已经处于 fulfilled 或 rejected 状态,那么相应的方法将会被立刻调用, 所以在异步操作的完成情况和它的绑定方法之间不存在竞争条件。)

你估计会认为这次看上去OK了吧?

很遗憾还是存在问题。。。

试想一下,如果在加载数据时偶尔出现异常,loadPromise最终变为一个rejected状态的promise对象。即使以后故障解决了,这时调用loadData()还是只能拿到一个rejected状态的promise对象。

判断一下rejected状态

这次我们判断一下rejected状态。很可惜,原生的Promise并没有提供同步API直接获取某个promise对象的状态,所以这里采取一个变通的办法。

var loadPromise = null;
//定义一个变量用来保存Promise是否处于rejected状态
var loadRejected = false;

var loadData = function(){
  //在加载数据时,如发现loadPromise为null或promise为rejected状态,才重新加载
  if(loadPromise === null || loadRejected) {
    //一旦准备加载数据,则重置rejected状态
    loadRejected = false;
    loadPromise = fetch('/').then(function(data){
      return data.statusText;
    }).then(undefined, function(){
      //如加载过程出现异常,则记录rejected状态
      loadRejected = true;
    });
  }
  return loadPromise;
};

var reloadData = function(){
  loadPromise = null;
  return loadData();
};

loadData().then(function(data){
  console.log(data);
});

仔细检查了好几遍,暂时没有发现其它问题。如有高手发现问题请通知我。

总结

HTML5中的Promise确实是个好特性,但用起来真的有很小心,不然很容易出问题。

参考

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏nummy

Tornado入门(三)【协程】

在Tornado中,协程是推荐使用的异步方式。协程使用yield关键字暂停或者恢复执行,而不是回调链的方式。

17330
来自专栏性能与架构

Zookeeper实例 - 分布式锁

需求场景 在分布式系统中,通常会有多个子系统需要操作同一资源,例如修改数据存储中的某一数据 这些子系统各自独立,操作共享资源时没有逻辑顺序,有可能会出现同时...

38250
来自专栏对角另一面

lodash源码分析之List缓存

本文为读 lodash 源码的第七篇,后续文章会更新到这个仓库中,欢迎 star:pocket-lodash

20780
来自专栏Java技术分享

Redis实现信息已读未读状态提示

前提: 假如现在有2个模块需要提示消息:只要存在用户在上个时间点之后没有看过的信息就提示用户有新的信息 思路如下: 使用hash存储用户上次看过的时间,使用so...

89360
来自专栏云瓣

Node.js 异步异闻录

提到 Node.js, 我们脑海就会浮现异步、非阻塞、单线程等关键词,进一步我们还会想到 buffer、模块机制、事件循环、进程、V8、libuv 等知识点。本...

42680
来自专栏向治洪

android应用资源预编译,编译和打包全解析

我们知道,在一个APK文件中,除了有代码文件之外,还有很多资源文件。这些资源文件是通过Android资源打包工具aapt(Android Asset P...

758100
来自专栏小灰灰

动手实现MVC: 1. Java 扫描并加载包路径下class文件

背景 用过spring框架之后,有个指定扫描包路径,然后自动实例化一些bean,这个过程还是比较有意思的,抽象一下,即下面三个点 如何扫描包路径下所有的clas...

26370
来自专栏Java技术分享

Redis实现信息已读未读状态提示

假如现在有2个模块需要提示消息:只要存在用户在上个时间点之后没有看过的信息就提示用户有新的信息

568100
来自专栏我叫刘半仙

【JDK并发包基础】并发容器详解

      Java.util.concurrent 包是专为 Java并发编程而设计的包,它下有很多编写好的工具,使用这些更高等的同步工具来编写代码,让我们的...

38480
来自专栏对角另一面

lodash源码分析之List缓存

昨日我沿着河岸/漫步到/芦苇弯腰喝水的地方 顺便请烟囱/在天空为我写一封长长的信 潦是潦草了些/而我的心意/则明亮亦如你窗前的烛光/稍有暧昧之处/势所难免...

28160

扫码关注云+社区

领取腾讯云代金券