

在 IPFS 的启动过程中 boot 函数就是一个大总管,控制了 IPFS 系统的启动整个过程。
上一篇文章中,我们从整体上了解了 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
waterfall 函数。在这里,总共要执行 3 个函数,我们以此来看这 3个函数。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)
})
}
如果是第一次进来,那么仓库还不存在,所以没办法打开,即 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 文件中。下一篇,我们仔细讲解这个函数的执行过程。调用 IPFS 对象的 start 方法,启动 IPFS 系统。这个函数我们在分析完初始过程中再来看。
(cb) => {
if (!doStart) {
return cb()
}
self.start((err) => {
if (err) return cb(Object.assign(err, { emitted: true }))
cb()
})
}
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 系统的启动整个过程。