docs: function resolve(tree, options) {
        var settings = _.defaults(options, {
          childNode: 'children',
          clone: false,
          attempts: 5,
          splice: true,
          isPromise: _.isFunction,
          isSplice: function isSplice(node, path, tree) {
            var parentNodePath = path.slice(0, -1);
            var parentNode = parentNodePath.length ? _.get(tree, parentNodePath) : tree; // For empty node paths return the main tree
  
            return _.isArray(node) && _.isArray(parentNode); // An array within an array?
          },
          wrapper: function wrapper(node) {
            return Promise.resolve(node());
          }
        });
  
        var base = settings.clone ? _.cloneDeep(tree) : tree;
        var dirty = true; // Whether we saw a node sweep return a function instead of a scalar - indicates a new sweep is required
  
        var splices = [];
  
        var resolver = function resolver(root, path) {
          var promiseQueue = [];
  
          _.forEach(root, function (child, childIndex) {
            if (_.isArray(child)) {
              // Scan children
              promiseQueue.push(resolver(child, path.concat([childIndex])));
            } else if (_.isPlainObject(child)) {
              // Scan an object
              promiseQueue.push(resolver(child, path.concat([childIndex])));
            } else if (_.isFunction(child)) {
              var nodePath = path.concat([childIndex]);
              promiseQueue.push(settings.wrapper(child, nodePath, base). // Wrap the function and expect it to return a promise
              then(function (res) {
                // Recursion - Does this look like a value that we should do another sweep though later?
                if (!dirty && _.isObject(res) && treeTools.hasSome(res, function (v) {
                  return settings.isPromise(v);
                })) {
                  dirty = true; // Returned a promise like object - mark sweep as dirty
                } // Set the tree path to the return value
  
  
                _.set(base, nodePath, res); // Does this value look like it should be spliced rather than set
  
  
                if (settings.splice && settings.isSplice(res, nodePath, base)) {
                  splices.push(nodePath);
                }
              }));
            } // Everything else - leave alone as already resolved values
  
          });
  
          return Promise.all(promiseQueue);
        };
  
        return Promise.resolve().then(function () {
          return new Promise(function (resolve, reject) {
            // Loop the resolver until we are out of attempts
            var attemptNext = function attemptNext() {
              if (--settings.attempts > 0 && dirty) {
                dirty = false; // Mark sweep as clean - will get dirty if resolver sees a function return
  
                resolver(base, []).then(attemptNext);
              } else {
                resolve();
              }
            };
  
            attemptNext();
          });
        }).then(function () {
          // Resolve all splices
          if (!settings.splice) return;
          splices.reverse().forEach(function (path) {
            // Reverse the array paths so we can work from the end backwards when splicing to maintain the index offsets
            var spliceParentPath = path.slice(0, -1);
            var spliceParent = spliceParentPath.length ? _.get(base, spliceParentPath) : base;
            var spliceOffset = path[path.length - 1];
            spliceParent.splice.apply(spliceParent, [spliceOffset, 1].concat(_toConsumableArray(_.get(base, path))));
          });
          return null;
        }).then(function () {
          return base;
        });
      }