2

我目前正在研究“Eloquent Javascript”第 3 版,目前处于最后一章,但我仍然不太了解第 11 章的“连接”requestType 代码。(您可以在以下网址查看整章:https ://eloquentjavascript.net/11_async.html#c_68Z9trrpeS )

当使用 'everywhere' 方法时,每个巢都有自己的 'connection' 属性,但一开始只有 1 个巢有它,然后它向所有附近的邻居广播连接,但是当邻居收到它时,他们没有'connections' 属性设置还没有(因为目前只有源嵌套有它),'requestType()' 方法如何从 null 分配一个新的 'connections'?

这是代码:

requestType("connections", (nest, {name, neighbors},
                            source) => {
  let connections = nest.state.connections;
  if (JSON.stringify(connections.get(name)) ==
      JSON.stringify(neighbors)) return;
  connections.set(name, neighbors);
  broadcastConnections(nest, name, source);
});

function broadcastConnections(nest, name, exceptFor = null) {
  for (let neighbor of nest.neighbors) {
    if (neighbor == exceptFor) continue;
    request(nest, neighbor, "connections", {
      name,
      neighbors: nest.state.connections.get(name)
    });
  }
}

everywhere(nest => {
  nest.state.connections = new Map;
  nest.state.connections.set(nest.name, nest.neighbors);
  broadcastConnections(nest, nest.name);
});

4

2 回答 2

2

我也在读这一章,希望对你有帮助!

首先我们看一下crow-tech.js中的wherewhere方法声明

everywhere(f) {
  for (let node of Object.values(this.nodes)) f(node);
}

所有这一切都是它接受一个回调函数f,然后对于我们网络中的每个节点/巢,我们调用f,将每个节点/巢作为参数传递。

现在,看看这个无处不在的调用:

everywhere(nest => {
  nest.state.connections = new Map;
  nest.state.connections.set(nest.name, nest.neighbors);
  broadcastConnections(nest, nest.name);
});

下面的匿名函数实际上是作为 f 传递给任何地方的。

nest => {
      nest.state.connections = new Map;
      nest.state.connections.set(nest.name, nest.neighbors);
      broadcastConnections(nest, nest.name);
    }

所以我们说,对于每个节点/巢,我们要实例化一个新的 Map 并将其设置为其状态的连接属性。此外,我们将首先在该 Map 中添加一个条目,键是巢的名称,值是它的直接邻居。

现在,让我们谈谈broadcastConnections(nest, nest.name)

function broadcastConnections(nest, name, exceptFor = null) {
  for (let neighbor of nest.neighbors) {
    if (neighbor == exceptFor) continue;
    request(nest, neighbor, "connections", {
      name,
      neighbors: nest.state.connections.get(name),
    });
  }
}

我们可以看到我们正在遍历我们的每个巢的直接邻居并在下面调用这个请求函数:

function request(nest, target, type, content) {
  return new Promise((resolve, reject) => {
    let done = false;
    function attempt(n) {
      nest.send(target, type, content, (failed, value) => {
        done = true;
        if (failed) reject(failed);
        else resolve(value);
      });
      setTimeout(() => {
        if (done) return;
        else if (n < 3) attempt(n + 1);
        else reject(new Timeout("Timed out"));
      }, 250);
    }
    attempt(1);
  });
}

然后,此请求函数从 crow-tech.js 文件调用节点/巢的发送方法

send(to, type, message, callback) {
  let toNode = this[$network].nodes[to];
  if (!toNode || !this.neighbors.includes(to))
    return callback(new Error(`${to} is not reachable from ${this.name}`));
  let handler = this[$network].types[type];
  if (!handler) return callback(new Error("Unknown request type " + type));
  if (Math.random() > 0.03)
    setTimeout(() => {
      try {
        handler(toNode, ser(message), this.name, (error, response) => {
          setTimeout(() => callback(error, ser(response)), 10);
        });
      } catch (e) {
        callback(e);
      }
    }, 10 + Math.floor(Math.random() * 10));
}

在 send 方法中,你可以看到我们最终在调用setTimeout(),这个函数基本上会将一个函数放入事件或消息队列中。本文提供了有关事件循环的更多信息:https ://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop

在我们的例子中,send 方法中的处理函数就是由这个函数定义的:

requestType("connections", (nest, { name, neighbors }, source) => {
  let connections = nest.state.connections;
  if (JSON.stringify(connections.get(name)) === JSON.stringify(neighbors))
    return;
  connections.set(name, neighbors);
  broadcastConnections(nest, name, source);
});

更具体地说,处理函数是这个匿名函数表达式:

(nest, { name, neighbors }, source) => {
      let connections = nest.state.connections;
      if (JSON.stringify(connections.get(name)) === JSON.stringify(neighbors))
        return;
      connections.set(name, neighbors);
      broadcastConnections(nest, name, source);
    }

为了最终将所有内容联系在一起,连接永远不会为空的原因是因为调用处理程序的 send 方法中的匿名函数实际上位于事件或消息队列中。事件或消息队列中的函数只会在当前事件循环堆栈为空时执行。

为每个节点/嵌套调用的这两行都已经在堆栈上:

  nest.state.connections = new Map;
  nest.state.connections.set(nest.name, nest.neighbors);

这就是为什么在调用“连接”类型的处理程序时,每个节点/嵌套的连接都是一个有效的 Map 实例,至少有 1 个条目。

于 2020-12-25T20:59:44.430 回答
0

在幕后,broadcastConnections() 使用 request() 来执行异步操作。因此,由 broadcastConnections() 触发的任何动作都发生在事件循环的下一个“滴答”中,这意味着到那时每个嵌套都有自己的 Map 集合。

于 2020-06-21T20:51:59.930 回答