4

我有a simple web我创建的应用程序,它使用localStorage将一组任务保存为字符串化的JSON。它也是Chrome网上应用店中的Chrome extension,代码库与在http://supersimpletasks.com上以VPS运行的扩展和网站完全相同。如何构建我的应用程序以在网站上运行时使用localStorage,以及如何作为Chrome应用程序运行时使用chrome.storage?

我想将我的扩展程序迁移到Chrome App,因此我可以访问chrome.storage.sync API,这将允许我的用户跨设备同步任务。如果我想存储超过5MB的数据,使用chrome.storage也会给我更多的灵活性。

但是,当我的应用程序由supersimpletasks.com提供时,chrome.storage不起作用 - 我需要使用localStorage。

据我所知,localStorage是同步的,chrome.storage是异步的,这意味着很多重写下面的方法。这两个方法负责检索任务并从localStorage保存任务。

@getAllTasks: -> 
    allTasks = localStorage.getItem(DB.db_key) 
    allTasks = JSON.parse(allTasks) || Arrays.default_data 

    allTasks 

@setAllTasks: (allTasks) -> 
    localStorage.setItem(DB.db_key, JSON.stringify(allTasks)) 
    Views.showTasks(allTasks) 

如何构建我的应用程序以根据环境使用localStorage或chrome.storage?我希望遇到什么问题?

回答

4

此问题的解决方案是创建您自己的存储API。您已经确定localStorage是同步的,而Chrome存储是异步的,但这只是简单地将所有内容视为异步而轻易解决的问题。

创建您自己的API,然后用它代替所有其他调用。代码中的快速查找/替换可以用新API替换localStorage调用。

function LocalStorageAsync() { 

    /** 
     * Parses a boolean from a string, or the boolean if an actual boolean argument is passed in. 
     * 
     * @param {String|Boolean} bool A string representation of a boolean value 
     * @return {Boolean} Returns a boolean value, if the string can be parsed as a bool. 
     */ 
    function parseBool(bool) { 
     if (typeof bool !== 'string' && typeof bool !== 'boolean') 
      throw new Error('bool is not of type boolean or string'); 
     if (typeof bool == 'boolean') return bool; 
     return bool === 'true' ? true : false; 
    } 

    /** 
    * store the key value pair and fire the callback function. 
    */ 
    this.setItem = function(key, value, callback) { 
     if(chrome && chrome.storage) { 
      chrome.storage.local.set({key: key, value: value}, callback); 
     } else { 
      var type = typeof value; 
      var serializedValue = value; 
      if(type === 'object') { 
       serializedValue = JSON.stringify(value); 
      } 
      value = type + '::typeOf::' + serializedValue; 
      window.localStorage.setItem(key, value); 
      callback(); 
     } 
    } 

    /** 
    * Get the item from storage and fire the callback. 
    */ 
    this.getItem = function(key, callback) { 
     if(chrome && chrome.storage) { 
      chrome.storage.local.get(key, callback); 
     } else { 
      var stronglyTypedValue = window.localStorage.getItem(key); 
      var type = stronglyTypedValue.split('::typeOf::')[0]; 
      var valueAsString = stronglyTypedValue.split('::typeOf::')[1]; 
      var value; 
      if(type === 'object') { 
       value = JSON.parse(valueAsString); 
      } else if(type === 'boolean') { 
       value = parseBool(valueAsString); 
      } else if(type === 'number') { 
       value = parseFloat(valueAsString); 
      } else if(type === 'string') { 
       value = valueAsString; 
      } 
      callback(value); 
     } 
    } 
} 


// usage example 
l = new LocalStorageAsync(); 
l.setItem('test',[1,2,3], function() {console.log('test');}); 
l.getItem('test', function(e) { console.log(e);}); 

的一个问题下面这个解决方案克服了,除了处理一切为异步的,是它也解释了所有的localStorage一切转换为字符串的事实。通过将类型信息保存为元数据,我们确保getItem操作产生的内容与进入的数据类型相同。

更重要的是,使用工厂模式的变体,您可以创建两个具体的内部子类并根据环境返回相应的代码:

function LocalStorageAsync() { 
    var private = {}; 

    private.LocalStorage = function() { 
     function parseBool(bool) { 
      if (typeof bool !== 'string' && typeof bool !== 'boolean') 
       throw new Error('bool is not of type boolean or string'); 
      if (typeof bool == 'boolean') return bool; 
       return bool === 'true' ? true : false; 
     } 
     this.setItem = function(key, value, callback) { /* localStorage impl... */ }; 
     this.getItem = function(key, callback) { /* ... */ }; 
    }; 

    private.ChromeStorage = function() { 
     this.setItem = function(key, value, callback) { /* chrome.storage impl... */ }; 
     this.getItem = function(key, callback) { /* ... */ }; 
    } 

    if(chrome && chrome.storage) 
     return new private.ChromeStorage(); 
    else 
     return new private.LocalStorage(); 
}; 
+0

谢谢!这种方法对我来说很有意义。将这些函数拆分成类似localStorageSetItem()/ localStorageGetItem()和chromeStorageSetItem()/ chromeStorageGetItem()的东西是否是一个好主意,然后在页面加载时创建一个存储对象来决定使用哪一个? – 2014-10-08 05:39:08

+0

@BenjaminHumphrey - 是的,除了我会使用类似工厂模式的东西,而是确保两个子类的接口都是相同的API和方法签名。然后构造函数或工厂方法可以处理返回适当的实现。我更新了这篇文章,举例说明了我过去如何处理这个问题。用法与上例相同,但实现更独立且独立。伟大的问题! – jmort253 2014-10-08 15:05:25

1

这是我最终得到的代码,它适用于我想要做的事情。

这不是真正的异步代码,特别是在存储API中的ChromeStorage.set()方法中,我没有使用回调函数。理想情况下,你会想要使用回调做一些错误处理。

的localStorage或chrome.storage

首先,代码,以确定是否要使用的localStorage或chrome.storage。该变量被附加到窗口,因此全局可用。

if !!window.chrome and chrome.storage 
    window.storageType = ChromeStorage 
    else 
    window.storageType = LocalStorage 

存储API

接下来,存储API这使得'Class' in Coffeescript.使用这不是一个完全的时刻抽象。我仍然有一些代码来处理从localStorage迁移到chrome.storage。 LocalStorage类是模仿异步的。

class LocalStorage 

    # Gets a generic value from localStorage given a particular key 
    # Parses the JSON so it's an object instead of a string 
    @get: (key, callback) -> 

    value = localStorage.getItem(key) 

    value = JSON.parse(value) 

    callback(value) 


    # Synchronously gets the stuff from localStorage 
    @getSync: (key) -> 

    value = localStorage.getItem(key) 

    JSON.parse(value) 


    # Sets something to localStorage given a key and value 
    @set: (key, value) -> 

    value = JSON.stringify(value) 

    localStorage.setItem(key, value) 


    # Removes something from localStorage given a key 
    @remove: (key) -> 
    localStorage.removeItem(key) 


class ChromeStorage 

    # Return all the tasks given the key 
    # At the moment the key is 'todo' for most calls 
    @get: (key, callback) -> 

    chrome.storage.sync.get key, (value) -> 
     value = value[key] || null || LocalStorage.getSync(key) 

     callback(value) 


    # Set all the tasks given the key 'todo' and the thing we're setting 
    # Usually a JSON array of all the tasks 
    @set: (key, value, callback) -> 

    params = {} 
    params[key] = value 

    chrome.storage.sync.set params,() -> 


    # Remove a whole entry from chrome.storage.sync given its key 
    @remove: (key) -> 
    chrome.storage.sync.remove key,() -> 


    # Listen for changes and run Views.showTasks when a change happens 
    if !!window.chrome and chrome.storage 

    chrome.storage.onChanged.addListener (changes, namespace) -> 
     for key of changes 
     if key == DB.db_key 
      storageChange = changes[key] 
      Views.showTasks(storageChange.newValue) 

使用例如

最后,下面是我如何使用存储API在我的代码的例子。该方法保存了一项新任务。 DB.db_key是一个表示存储中使用的密钥的变量。

# Sets a new task 
    # Receives name which is in the input 
    @setNewTask: (name) -> 

    # Only do this stuff if the input isn't blank 
    unless name == '' 

     # Sends the task to @createTask() to make a new task 
     newTask = @createTask(name) 

     # Get all the tasks 
     window.storageType.get DB.db_key, (allTasks) -> 

     # Adds that new task to the end of the array 
     allTasks.push newTask 

     # Save all the tasks 
     window.storageType.set(DB.db_key, allTasks) 

     # Show the tasks 
     Views.showTasks(allTasks) 

GitHub的库是在这里:https://github.com/humphreybc/super-simple-tasks

+0

非常棒,您同时拥有同步获取和异步获取,但请注意,为了在两种存储类型之间建立一个通用接口,并让消费者使用这两种存储类型,您需要将它们视为异步。值得注意的是,某个地方对未来的访问者来说很清楚。希望这可以帮助!顺便说一句,在CoffeeScript中看到这一点很有趣。 – jmort253 2014-10-15 14:13:02

+1

谢谢 - 是的,这绝对是我需要改变的东西,如果我将其作为一个库来发布,以供其他项目使用。我的计划是增加对Firebase的支持,以便API可以处理三种不同的存储方法。然后我会考虑用JavaScript重写它并将其作为其他库的发布。谢谢你的帮助! – 2014-10-15 23:46:04

相关问题