上一篇文章中,我們從整體上了解了 IPFS 的啟動,今天我們就繼續深入看下 boot
函式是怎麼真正啟動系統的,這個函式位於 core/boot.js
檔案中。
在開始看boot
函式之前,我們先大致講下 async 類庫,Async 是一個實用程式模組,它提供了直接,強大的函式來處理非同步 JavaScript。這裡簡單講下 waterfall、parallel、series 等3個函式,這3個函式會頻繁用到。waterfall 函式,接收一個函式陣列或物件和一個回撥函式,首先呼叫第一個函式(或第一個 Key 對應的函式),以它的結果為引數呼叫後一個函式,再以後一個函式返回的結果呼叫下一個函式,以此類推,當所有函式呼叫完成後,以最後一個函式返回的結果為引數呼叫使用者指定的回撥函式。如果其中某個函式丟擲異常,下面的函式不會被執行,會立刻把錯誤物件傳遞給指定的回撥函式。
parallel 函式,接收一個函式陣列或物件和一個回撥函式,陣列中的函式會並行執行執行而不用等待前面的函式完成,當所有函式呼叫完成後,把所有函式的執行結果組成一個陣列,傳遞給最終的回撥函式。如果中某個函式丟擲異常,會立刻把錯誤物件傳遞給指定的回撥函式。
series 函式,接收一個函式陣列或物件和一個回撥函式,陣列中的函式會序列執行,即前一個執行完成之後才會繼續執行下一個。如果其中某個函式丟擲異常,下面的函式不會被執行,會立刻把錯誤物件傳遞給指定的回撥函式。
boot
函式執行流程如下
- 初始化用到的幾個變數。
const options = self._options const doInit = options.init const doStart = options.start
- 呼叫 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 才真正啟動成功,使用者可以用它做任何想做的事情。
- 首先,執行第 1 個函式。函式首先檢查倉庫狀態是否不是關閉的,如果倉庫不是關閉的就直接呼叫第 2 個函式,否則呼叫倉庫的
透過上面的分析,我們發現 IPFS 的啟動整體上分為3個步驟,1)開啟倉庫;2)IPFS 初始化;3)IPFS 啟動,而 boot
函式就是一個大總管,控制了 IPFS 系統的啟動整個過程。