mt logoMyToken
总市值:
0%
恐慌指数:
0%
币种:--
平台 --
ETH Gas:--
EN
USD
APP
Ap Store QR Code

Scan Download

精通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 系统的启动整个过程。

免责声明:本文版权归原作者所有,不代表MyToken(www.mytokencap.com)观点和立场;如有关于内容、版权等问题,请与我们联系。