【问题标题】:How can I structure my app to use localStorage when running on a website, and chrome.storage when running as a Chrome App?如何构建我的应用程序以在网站上运行时使用 localStorage,并在作为 Chrome 应用程序运行时使用 chrome.storage?
【发布时间】:2014-12-02 15:43:06
【问题描述】:

我有 a simple web 我构建的应用程序,它使用 localStorage 将一组任务保存为字符串化 JSON。它也是 Chrome Web Store 上的 Chrome extension,扩展程序和在 http://supersimpletasks.com 的 VPS 上运行的站点的代码库完全相同。

我想将我的扩展程序迁移到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?我会遇到什么问题?

【问题讨论】:

    标签: javascript html google-chrome google-chrome-extension local-storage


    【解决方案1】:

    解决这个问题的方法是创建自己的存储 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();
    };
    

    【讨论】:

    • 谢谢!这种方法对我来说很有意义。将这些函数拆分为 localStorageSetItem() / localStorageGetItem() 和 chromeStorageSetItem() / chromeStorageGetItem() 之类的函数,然后在页面加载时创建一个存储对象以确定使用哪个函数是否是个好主意?
    • @BenjaminHumphrey - 是的,除了我会使用工厂模式之类的东西,而是确保两个子类的接口是相同的 API 和方法签名。然后构造函数或工厂方法可以处理返回适当的实现。我更新了这篇文章,举例说明了我过去是如何处理这个问题的。用法与上面的示例相同,但实现更加独立和自包含。好问题!
    【解决方案2】:

    这是我最终得到的代码,它非常适合我想做的事情。

    这不是真正的异步代码,尤其是存储 API 中的 ChromeStorage.set() 方法,我没有使用回调。理想情况下,您会希望使用回调来进行一些错误处理。

    localStorage 或 chrome.storage

    首先,判断是使用localStorage还是chrome.storage的代码。该变量被附加到窗口中,因此它在全局范围内可用。

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

    存储 API

    接下来是使用'Class' in Coffeescript.的存储API,目前还没有完全抽象出来。我仍然有一些代码来处理从 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)
    

    使用示例

    最后,这是我如何在代码中使用 Storage 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

    【讨论】:

    • 同时拥有同步获取和异步获取真是太好了,但请注意,为了在两种存储类型之间建立通用接口,并让您的消费者使用这两种存储类型,您需要将两者都视为异步。可能值得注意的是,在某个地方对未来的访问者来说很清楚。希望这可以帮助!顺便说一句,在 CoffeeScript 中看到这一点很有趣。
    • 谢谢 - 是的,如果我要将其发布为供其他项目使用的库,那绝对是我需要更改的内容。我的计划是添加对 Firebase 的支持,以便 API 可以处理三种不同的存储方法。然后我会考虑用 JavaScript 重写它并作为库发布给其他人。感谢您的帮助!
    猜你喜欢
    • 2015-03-27
    • 1970-01-01
    • 1970-01-01
    • 2023-01-09
    • 1970-01-01
    • 2021-07-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多