精通IPFS:IPFS 启动之 boot 函数
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 个函数,否则调用仓库的
boot
函数就是一个大总管,控制了 IPFS 系统的启动整个过程。