精通IPFS:IPFS 啟動之 boot 函式

買賣虛擬貨幣

上一篇文章中,我們從整體上了解了 IPFS 的啟動,今天我們就繼續深入看下 boot 函式是怎麼真正啟動系統的,這個函式位於 core/boot.js 檔案中。

在開始看 boot 函式之前,我們先大致講下 async 類庫,Async 是一個實用程式模組,它提供了直接,強大的函式來處理非同步 JavaScript。這裡簡單講下 waterfall、parallel、series 等3個函式,這3個函式會頻繁用到。

waterfall 函式,接收一個函式陣列或物件和一個回撥函式,首先呼叫第一個函式(或第一個 Key 對應的函式),以它的結果為引數呼叫後一個函式,再以後一個函式返回的結果呼叫下一個函式,以此類推,當所有函式呼叫完成後,以最後一個函式返回的結果為引數呼叫使用者指定的回撥函式。如果其中某個函式丟擲異常,下面的函式不會被執行,會立刻把錯誤物件傳遞給指定的回撥函式。

parallel 函式,接收一個函式陣列或物件和一個回撥函式,陣列中的函式會並行執行執行而不用等待前面的函式完成,當所有函式呼叫完成後,把所有函式的執行結果組成一個陣列,傳遞給最終的回撥函式。如果中某個函式丟擲異常,會立刻把錯誤物件傳遞給指定的回撥函式。

series 函式,接收一個函式陣列或物件和一個回撥函式,陣列中的函式會序列執行,即前一個執行完成之後才會繼續執行下一個。如果其中某個函式丟擲異常,下面的函式不會被執行,會立刻把錯誤物件傳遞給指定的回撥函式。

boot 函式執行流程如下

  1. 初始化用到的幾個變數。
    const options = self._options
    const doInit = options.init
    const doStart = options.start
    
  2. 呼叫 async 類庫的 waterfall 函式。在這裡,總共要執行 3 個函式,我們以此來看這 3個函式。
    • 首先,執行第 1 個函式。函式首先檢查倉庫狀態是否不是關閉的,如果倉庫不是關閉的就直接呼叫第 2 個函式,否則呼叫倉庫的 open 方法(位於 ipfs-repo 專案 index.js 檔案中),開啟倉庫。

      倉庫的 open 方法的主體也是一個 waterfall 函式。倉庫的 waterfall 函式內部,首先呼叫 root 物件的 open 方法開啟主目錄(預設倉庫採用的是檔案系統儲存資料,用的是 datastore-fs 類庫),因為主目錄在倉庫物件初始化時候已經建立好了,所以這個方法什麼不做,接下來呼叫 _isInitialized 方法檢查倉庫是否已經初始化過,這個方法會檢查配置檔案、規格檔案、版本檔案是否存在。對於第一次進來這咱情況,這些檔案還不存在,方法直接丟擲異常,導致 _isInitialized 下面的所有方法不再執行,流程直接到指定的錯誤處理中。又因為這個時候鎖定檔案也不存在,所以直接呼叫 callback(err) 方法,從而回到 open 方法的回撥函式中。而對於不是第一次進來的情況,具體處理詳見 init 函式執行分析。

      倉庫 open 方法程式碼如下,後面我們還會遇到這個函式的,這裡不細說。

      open (callback) {
          if (!this.closed) {
            setImmediate(() => callback(new Error('repo is already open')))
            return // early
          }
          waterfall([
            (cb) => this.root.open(ignoringAlreadyOpened(cb)),
            (cb) => this._isInitialized(cb),
            (cb) => this._openLock(this.path, cb),
            (lck, cb) => {
              log('aquired repo.lock')
              this.lockfile = lck
              cb()
            },
            (cb) => {
              this.datastore = backends.create('datastore', path.join(this.path, 'datastore'), this.options)
              const blocksBaseStore = backends.create('blocks', path.join(this.path, 'blocks'), this.options)
              blockstore(
                blocksBaseStore,
                this.options.storageBackendOptions.blocks,
                cb)
            },
            (blocks, cb) => {
              this.blocks = blocks
              cb()
            },
            (cb) => {
              log('creating keystore')
              this.keys = backends.create('keys', path.join(this.path, 'keys'), this.options)
              cb()
            },
            (cb) => {
              this.closed = false
              log('all opened')
              cb()
            }
          ], (err) => {
            if (err && this.lockfile) {
              this._closeLock((err2) => {
                if (!err2) {
                  this.lockfile = null
                } else {
                  log('error removing lock', err2)
                }
                callback(err)
              })
            } else {
              callback(err)
            }
          })
      }
      
      在 open 方法的回撥函式中,呼叫 isRepoUninitializedError 方法,檢查錯誤的原因,我們這裡的原因是倉庫還未初始化,所以這個方法返回真,所以用 false 呼叫第二個函式。

      第 1個函式的程式碼如下:

      (cb) => {
        if (!self._repo.closed) {
          return cb(null, true)
        }
        // 開啟倉庫
        self._repo.open((err, res) => {
          if (isRepoUninitializedError(err)) return cb(null, false)
          if (err) return cb(err)
          cb(null, true)
        })
      }
      
    • 接下來,執行第 2個函式。如果不是第一次進來,那麼倉庫已經存在,則直接開啟倉庫。

      如果是第一次進來,那麼倉庫還不存在,所以沒辦法開啟,即 repoOpened 引數為假,所以跳過最上面的初始化。然後,檢查 doInit 變數是否為真,如果為真,則根據指定的選項來初始化倉庫。在這裡 doInit變數的值來自於選項中的 init 屬性,這個屬性只是一個簡單的真值,所以使用預設的配置來初始化。

      第 2個函式的程式碼如下:

      (repoOpened, cb) => {
        if (repoOpened) {
          return self.init({ repo: self._repo }, (err) => {
            if (err) return cb(Object.assign(err, { emitted: true }))
            cb()
          })
        }
        // 如果倉庫不存在,這裡需要進行初始化。
        if (doInit) {
          const initOptions = Object.assign(
            { bits: 2048, pass: self._options.pass },
            typeof options.init === 'object' ? options.init : {}
          )
          return self.init(initOptions, (err) => {
            if (err) return cb(Object.assign(err, { emitted: true }))
            cb()
          })
        }
        cb()
      }
      
      注意,在 JS 中真值不一定僅僅只一個 true,也可能是一個物件,一個函式,一個陣列等,所在這裡檢測是否為真,只是檢測使用者有沒有指定這個配置而已,並且確保不是 false 而已。
      上面 self 指的是 IPFS 物件,init 方法位於 core/components/init.js 檔案中。下一篇,我們仔細講解這個函式的執行過程。
    • 接下來,執行第 3個函式。檢查是否不需要啟動,如果是則直接呼叫最終的回撥函式。

      呼叫 IPFS 物件的 start 方法,啟動 IPFS 系統。這個函式我們在分析完初始過程中再來看。

      (cb) => {
        if (!doStart) {
          return cb()
        }
        self.start((err) => {
          if (err) return cb(Object.assign(err, { emitted: true }))
          cb()
        })
      }
      
    • 接下來,執行最終的回撥函式。如果前面 3個函式都沒有,則觸發 IPFS 物件的 ready 事件;如果有錯誤,則觸發相應的錯誤。
      (err) => {
          if (err) {
            if (!err.emitted) {
              self.emit('error', err)
            }
            return
          }
          self.log('booted')
          self.emit('ready')
      }
      
      當 waterfall 函式執行完成後,我們的 IPFS 才真正啟動成功,使用者可以用它做任何想做的事情。

透過上面的分析,我們發現 IPFS 的啟動整體上分為3個步驟,1)開啟倉庫;2)IPFS 初始化;3)IPFS 啟動,而 boot 函式就是一個大總管,控制了 IPFS 系統的啟動整個過程。

免責聲明:

  1. 本文版權歸原作者所有,僅代表作者本人觀點,不代表鏈報觀點或立場。
  2. 如發現文章、圖片等侵權行爲,侵權責任將由作者本人承擔。
  3. 鏈報僅提供相關項目信息,不構成任何投資建議

推荐阅读

;