MetaMask/obs-store

https://github.com/MetaMask/obs-storegit

ObservableStore

ObservableStore is a synchronous in-memory store for a single value, that you can subscribe to updates ongithub

ObservableStore是一個內存中的能夠訂閱更新的同步存儲,只存儲一個值瀏覽器

const store = new ObservableStore(initState)
store.subscribe(function showValue(value) {
  console.log('saw value:', value)
})

//經過調用putState()來存儲值 store.putState(
5) // "saw value: 5" ,存儲了5 store.putState(true) // "saw value: true" ,true覆蓋了以前的5的值 store.putState({ hello: 'world' }) // "saw value: { hello: 'world' }" ,{ hello: 'world' }覆蓋了以前的true的值 console.log(store.getState().hello) // "world" ,經過調用getState()函數來獲得存儲的值
從上面的例子能夠看出能且只可以存儲一個值

streams

Each ObservableStore can be turned into an ObservableStoreStream. An ObservableStoreStream is a duplex stream that you can pipe new values into it or pipe its updated values out of it.app

ObservableStore能夠轉換成ObservableStoreStream流,而且是一個雙工流函數

Special behavior: Doesnt buffer outgoing updates, writes latest state to dest on pipe.oop

不緩衝輸出更新,將最新狀態寫到管道dest上測試

const pipe = require('pump')
const asStream = require('obs-store/lib/asStream')

const storeOne = new ObservableStore(initState)
const storeTwo = new ObservableStore()

pipe(//至關於asStream(storeOne).pipe(transformStream).pipe(asStream(storeTwo)),並且使用pump監聽錯誤
  asStream(storeOne),
  transformStream,
  asStream(storeTwo)
)

 

Changelog

3.0.0

ObservableStore are no longer streams. You can create streams via asStream.ui

經過asStream來建立ObservableStoreStreamthis

obs-store/index.jsspa

'use strict'

const extend = require('xtend')
const EventEmitter = require('events')

class ObservableStore extends EventEmitter {

  constructor (initState = {}) {
    super()
    // set init state
    this._state = initState
  }

  // wrapper around internal getState
  getState () {//輸出值
    return this._getState()
  }
  
  // wrapper around internal putState
  putState (newState) {
    this._putState(newState)//存儲newStatethis.emit('update', newState)//並觸發subscribe中的'update'事件,並調用相應的handler函數
  }

  updateState (partialState) {//更改裏面的一部分的值
    // if non-null object, merge
    if (partialState && typeof partialState === 'object') {
      const state = this.getState()
      const newState = Object.assign({}, state, partialState)
      this.putState(newState)
    // if not object, use new value
    } else {
      this.putState(partialState)
    }
  }

  // subscribe to changes
  subscribe (handler) {
    this.on('update', handler)
  }

  // unsubscribe to changes
  unsubscribe (handler) {
    this.removeListener('update', handler)//移除'update'事件
  }

  //
  // private
  //

  // read from persistence
  _getState () {
    return this._state
  }

  // write to persistence
  _putState (newState) {
    this._state = newState
  }

}

module.exports = ObservableStore

 

其調用的lib庫:

obs-store/lib/asStream.js

做用是將ObsStore轉成ObsStoreStream流,並定義流相應的一些方法

const DuplexStream = require('stream').Duplex

module.exports = asStream


function asStream(obsStore) {
  return new ObsStoreStream(obsStore)
}

//
//
//
//

class ObsStoreStream extends DuplexStream {

  constructor(obsStore) {
    super({
      // pass values, not serializations
      objectMode: true,
    })
    // dont buffer outgoing updates
    this.resume()
    // save handler so we can unsubscribe later
    this.handler = (state) => this.push(state)
    // subscribe to obsStore changes
    this.obsStore = obsStore
    this.obsStore.subscribe(this.handler)
  }

  // emit current state on new destination
  pipe (dest, options) {//調用pipe函數,將obsStore.getState()值傳到dest
    const result = DuplexStream.prototype.pipe.call(this, dest, options)
    dest.write(this.obsStore.getState())
    return result
  }

  // write from incomming stream to state
  _write (chunk, encoding, callback) {
    this.obsStore.putState(chunk)
    callback()
  }

  // noop - outgoing stream is asking us if we have data we arent giving it
  _read (size) { }

  // unsubscribe from event emitter
  _destroy (err, callback) {
    this.obsStore.unsubscribe(this.handler);
    super._destroy(err, callback)
  }

}

 

obs-store/lib/localStorage.js

設置一個繼承ObservableStoreLocalStorageStore,頁面端的global.localStorage就是這個

'use strict'

const ObservableStore = require('../')


class LocalStorageStore extends ObservableStore {

  constructor (opts = {}) {
    if (!global.localStorage) throw new Error('LocalStorageStore - can\'t find localStorage.')//global.localStorage即瀏覽器自己是否有本地存儲
    super()
    this._storageKey = opts.storageKey
    if (!this._storageKey) throw new Error('LocalStorageStore - no storageKey specified.')
  }

  //
  // private
  //

  // read from persistence
  _getState () {
    const serialized = global.localStorage.getItem(this._storageKey)//不一樣的_storageKey對應的是本地存儲的不一樣內容
    return serialized ? JSON.parse(serialized) : undefined
  }

  // write to persistence
  _putState (newState) {
    const serialized = JSON.stringify(newState)
    return global.localStorage.setItem(this._storageKey, serialized)
  }

}

module.exports = LocalStorageStore

還有一些別的,可是先無論,以後用到再說

 

 

測試:

 

'use strict'

const test = require('tape')const pipe = streamUtils.pipeconst ObservableStore = require('../')
const asStream = require('../lib/asStream')

const TEST_WAIT = 200


test('basic stream', function(t){
  t.plan(2)

  const initState = 'init'
  const nextState = 'next'

  const storeOne = new ObservableStore(initState)//在初始化時就存儲值initState
  const storeTwo = new ObservableStore()//在初始化時並無存儲值
  storeTwo.once('update', (value) => {//監聽一次'update'事件
    initValueCheck(value)//查看value值與initState值是否相同
    storeTwo.once('update', nextValueCheck)//再監聽一次查看與nextState的值是否相同
  })

  pipe(//將storeOne流中的值initState傳給storeTwo流,storeTwo調用putState時會調用一次'update'事件,查看是否於initState值相同
    asStream(storeOne),
    asStream(storeTwo)
  )

  storeOne.putState(nextState)//將storeOne中的值從initState改變成nextState,這裏又會觸發一次'update'事件,此次是查看是否於nextState相同

  function initValueCheck(value){
    t.equal(value, initState, 'storeTwo subscribed: state is initState')
  }

  function nextValueCheck(value){
    t.equal(value, nextState, 'storeTwo subscribed: state is nextState')
  }

})

test('double stream', function(t){
  t.plan(4)

  const initState = 'init'
  const nextState = 'next'

  const storeOne = new ObservableStore(initState)
  const storeTwo = new ObservableStore()
  storeTwo.once('update', (initValue) => {
    initValueCheck('storeTwo', initValue)
    storeTwo.once('update', (nextValue) => nextValueCheck('storeTwo', nextValue))
  })

  const storeThree = new ObservableStore()
  storeThree.once('update', (initValue) => {
    initValueCheck('storeThree', initValue)
    storeThree.once('update', (nextValue) => nextValueCheck('storeThree', nextValue))
  })

  pipe(
    asStream(storeOne),
    asStream(storeTwo)//storeTwo觸發一次'update'
  )

  pipe(
    asStream(storeOne),
    asStream(storeThree)//storeThree觸發一次'update'
  )

  storeOne.putState(nextState)//將會致使storeTwostoreThree再分別觸發一次

  function initValueCheck(label, value){
    t.equal(value, initState, `${label} subscribed: state is initState`)
  }

  function nextValueCheck(label, value){
    t.equal(value, nextState, `${label} subscribed: state is nextState`)
  }

})

 

 

LocalStorageStore的測試,首先localStorage是定義好的,而且是有storageKey值的,如{ storageKey: 'test' }

'use strict'

const test = require('tape')
const LocalStorageStore = require('../lib/localStorage')


test('localStorage - localStorage presence validation', function(t){
  t.plan(2)

  t.notOk(global.localStorage, 'global.localStorage not defined')

  t.throws(() => {
    new LocalStorageStore({ storageKey: 'test' })
  }, Error, 'throws error when localStorage is not defined')

})

test('localStorage - storageKey validation', function(t){
  t.plan(2)

  global.localStorage = createLocalStorage()

  t.ok(global.localStorage, 'global.localStorage is defined')

  t.throws(() => {
    new LocalStorageStore()
  }, Error, 'throws error when opts.storageKey is not defined')

})

test('localStorage - basic test', function(t){
  t.plan(2)

  global.localStorage = createLocalStorage()

  t.ok(global.localStorage, 'global.localStorage is defined')

  const store = new LocalStorageStore({ storageKey: 'test' })
  store.putState(42)

  t.equal(store.getState(), 42, 'store works roundtrips values great')
})

test('localStorage - obj test', function(t){
  t.plan(2)

  global.localStorage = createLocalStorage()

  t.ok(global.localStorage, 'global.localStorage is defined')

  const store = new LocalStorageStore({ storageKey: 'test' })
  store.putState({ a: 123 })

  t.deepEqual(store.getState(), { a: 123 }, 'store works roundtrips obj values great')
})

function createLocalStorage() {
  const values = {}
  const localStorage = {}
  localStorage.getItem = (key) => values[key]
  localStorage.setItem = (key, value) => values[key] = value
  return localStorage
}
本站公眾號
   歡迎關注本站公眾號,獲取更多信息