Skip to content

Latest commit

 

History

History
269 lines (226 loc) · 10.7 KB

06.未来的函数:生成器和promise.md

File metadata and controls

269 lines (226 loc) · 10.7 KB

06.未来的函数:生成器和 promise

  • 通过生成器让函数持续执行
  • 使用 promise 处理异步任务
  • 使用生成器和 promise 书写优雅代码

使用生成器和 promise 编写优雅的异步代码

try {
  var ninjas = syncGetJSON('ninjas.json');
  var missions = syncGetJSON(ninjas[0].missionsUrl);
  var missionDetails = syncGetJSON(missions[0].detailsUrl);
  //Study the mission description
} catch (e) {
  //Oh no, we weren't able to get the mission details
}

改为回调:

getJSON('ninjas.json', function(err, ninjas) {
  if (err) {
    console.log('Error fetching list of ninjas', err);
    return;
  }
  getJSON(ninjas[0].missionsUrl, function(err, missions) {
    if (err) {
      console.log('Error locating ninja missions', err);
      return;
    }
    getJSON(missions[0].detailsUrl, function(err, missionDetails) {
      if (err) {
        console.log('Error locating mission details', err);
        return;
      }
      //Study the intel plan
    });
  });
});

生成器函数:

async(function*() {
  try {
    const ninjas = yield getJSON('ninjas.json');
    const missions = yield getJSON(ninjas[0].missionsUrl);
    const missionDescription = yield getJSON(missions[0].detailsUrl);
    //Study the mission details
  } catch (e) {
    //Oh no, we weren't able to get the mission details
  }
});

使用生成器函数

生成器函数和标准函数非常不同。对初学者来说,调用生成器并不会执行生成器函数,相反,它会创建一个叫作迭代器(iterator)的对象。

通过迭代器对象控制生成器

调用生成器函数不一定会执行生成器函数体。通过创建迭代器对象,可以与生成器通信。

function* WeaponGenerator() {
  yield 'Katana';
  yield 'Wakizashi';
}
const weaponsIterator = WeaponGenerator();
const result1 = weaponsIterator.next();
console.log(
  typeof result1 === 'object' && result1.value === 'Katana' && !result1.done
); // true
const result2 = weaponsIterator.next();
console.log(
  typeof result2 === 'object' && result2.value === 'Wakizashi' && !result2.done
); // true
const result3 = weaponsIterator.next();
console.log(
  typeof result3 === 'object' && result3.value === undefined && result3.done
); // true
  • 调用生成器后,就会创建一个迭代器(iterator): const weaponsIterator = WeaponGenerator();
  • 迭代器用于控制生成器的执行。迭代器对象暴露的最基本接口是 next 方法。这个方 法可以用来向生成器请求一个值,从而控制生成器: const result1 = weaponsIterator.next();
  • next 函数调用后,生成器就开始执行代码,当代码执行到 yield 关键字时,就会生成一个中间结果(生成值序列中的一项),然后返回一个新对象,其中封装了结果值和一个指示完成的指示器。
  • 每当生成一个当前值后,生成器就会非阻塞地挂起执行,随后耐心等待下一次值请求的到达。

对迭代器进行迭代

function* WeaponGenerator() {
  yield 'Katana';
  yield 'Wakizashi';
}

const weaponsIterator = WeaponGenerator();
let item;
while (!(item = weaponsIterator.next()).done) {
  console.log(item !== null, item.value);
}

把执行权交给下一个生成器

function* WarriorGenerator() {
  yield 'Sun Tzu';
  yield* NinjaGenerator();
  yield 'Genghis Khan';
}
function* NinjaGenerator() {
  yield 'Hattori';
  yield 'Yoshi';
}
for (let warrior of WarriorGenerator()) {
  console.log(warrior !== null, warrior);
}

// true "Sun Tzu"
// true "Hattori"
// true "Yoshi"
// true "Genghis Khan"

使用生成器

用生成器生成 ID 序列

function* IdGenerator() {
  let id = 0;
  while (true) {
    yield ++id;
  }
}
const idIterator = IdGenerator();
const ninja1 = { id: idIterator.next().value };
const ninja2 = { id: idIterator.next().value };
const ninja3 = { id: idIterator.next().value };
console.log(ninja1.id === 1, 'First ninja has id 1');
console.log(ninja2.id === 2, 'Second ninja has id 2');
console.log(ninja3.id === 3, 'Third ninja has id 3');

// true "First ninja has id 1"
// true "Second ninja has id 2"
// true "Third ninja has id 3"

迭代器中包含一个局部变量 id,其代表了 ID 计数器。局部变量 id 仅能在该生成器中被访问,故而完全不必担心有人会不小心在代码的其他地方修改 id 值。

标准函数中一般不应该书写无限循环的代码。但在生成器中没问题!当生成器遇到了一 个 yield 语句,它就会一直挂起执行直到下次调用 next 方法,所以只有每次调用一次 next 方法,while 循环才会迭代一次并返回下一个 ID 值。

使用迭代器遍历 DOM 树

递归遍历:

<div id="subTree">
  <form>
    <input type="text"/>
  </form>
  <p>Paragraph</p>
  <span>Span</span>
</div>
<script>
  function traverseDOM(element, callback) {
    callback(element);
    element = element.firstElementChild;
    while (element) {
      traverseDOM(element, callback);
      element = element.nextElementSibling;
    }
  }
  const subTree = document.getElementById("subTree");
  traverseDOM(subTree, function(element) {
    console.log(element !== null, element.nodeName);
  });
</script>

生成器:

function* DomTraversal(element) {
  yield element;
  element = element.firstElementChild;
  while (element) {
    yield* DomTraversal(element);
    element = element.nextElementSibling;
  }
}
const subTree = document.getElementById('subTree');
for (let element of DomTraversal(subTree)) {
  console.log(element !== null, element.nodeName);
}

探索生成器内部构成

我们已经知道了调用一个生成器不会实际执行它。相反,它创建了一个新的迭代器, 通过该迭代器我们才能从生成器中请求值。在生成器生成(或让渡)了一个值后,生成 器会挂起执行并等待下一个请求的到来。在某种方面来说,生成器的工作更像是一个小 程序,一个在状态中运动的状态机。

  • 挂起开始——创建了一个生成器后,它最先以这种状态开始。其中的任何代码都未执行。
  • 执行——生成器中的代码已执行。执行要么是刚开始,要么是从上次挂起的时候继续的。当生成器对应的迭代器调用了 next 方法,并且当前存在可执行的代码时,生成器都会转移到这个状态。
  • 挂起让渡——当生成器在执行过程中遇到了一个 yield 表达式,它会创建一个包含着返回值的新对象,随后再挂起执行。生成器在这个状态暂停并等待继续执行。
  • 完成——在生成器执行期间,如果代码执行到 return 语句或者全部代码执行完毕,生成器就进入该状态。

使用 promise

把生成器和 promise 相结合

async(function*() {
  try {
    const ninjas = yield getJSON('data/ninjas.json');
    const missions = yield getJSON(ninjas[0].missionsUrl);
    const missionDescription = yield getJSON(missions[0].detailsUrl);
    //Study the mission details
  } catch (e) {
    //Oh no, we weren't able to get the mission details
  }
});

function async(generator) { // 定义一个辅助函数,用于对我们定义的生成器执行操作
  var iterator = generator(); // 创建一个迭代器,进而我们可以控制生成器
  function handle(iteratorResult) { // 定义函数 handle,用于对生成器产生的每个值进行处理
    if (iteratorResult.done) { //  当生成器没有更多结果返回时停止执行
      return;
    }
    const iteratorValue = iteratorResult.value;
    if (iteratorValue instanceof Promise) { // 如果生成的值是一个promise,则对其注册成 功和失败回调。这是异步处理的部分。如果 promise成功返回,则恢复生成器的执行并传 入 promise 的返回结果。如果遇到错误,则向生成器抛出异常
      iteratorValue
        .then(res => handle(iterator.next(res)))
        .catch(err => iterator.throw(err));
    }
  }
  try {
    handle(iterator.next()); // 重启生成器的执行
  } catch (e) {
    iterator.throw(e);
  }
}

async 函数获取了一个生成器,调用它并创建了一个迭代器用来恢复生成器的执行。 在 async 函数内,我们声明了一个处理函数用于处理从生成器中返回的值——迭代器的 一次“迭代”。如果生成器的结果是一个被成功兑现的承诺,我们就是用迭代器的 next 方法把承诺的值返回给生成器并恢复执行。如果出现错误,承诺被违背,我们就使用迭 代器的 throw 方法(告诉过你迟早能派上用场了)抛出一个异常。直到生成器的工作完 成前,我们都会一直重复这几个操作。

这只是个粗略的草稿, 一个最小化的代码应该把生成器和 promise 结合在一起。不推荐 在生产环境下使用这种代码。

  • 函数是第一类对象——我们向 async 函数传入了一个参数,该参数也是函数。
  • 生成器函数——用它的特性来挂起和恢复执行。
  • promise——帮我们处理异步代码。
  • 回调函数——在 promise 对象上注册成功和失败的回调函数。
  • 箭头函数——箭头函数的简洁适合用在回调函数上。
  • 闭包——在我们控制生成器的过程中, 迭代器在 async 函数内被创建,随之我们在 promise 的回调函数内通过闭包来获取该迭代器。

面向未来的 async 函数

小结

  • 生成器是一种不会在同时输出所有值序列的函数,而是基于每次的请求生成值。
  • 不同于标准函数,生成器可以挂起和回复它们的执行状态。当生成器生成了一个值后,它将会在不阻塞主线程的基础上挂起执行,随后静静地等待下次请求。
  • 生成器通过在 function 后面加一个星号(*)来定义。在生成器函数体内,我们可以使用新的关键字 yield 来生成一个值并挂起生成器的执行。如果我们想让渡到另一个生成器中,可以使用 yield 操作符。
  • 在我们控制生成器的执行过程中,通过使用迭代器的 next 方法调用一个生成器,它能够创建一个迭代器对象。除此之外,我们还能够通过 next 函数向生成器中传入值。
  • promise 是计算结果值的一个占位符,它是对我们最终会得到异步计算结果的一个保证。promise 既可以成功也可以失败,一旦设定好了,就不能够有更多改变。
  • promise 显著地简化了我们处理异步代码的过程。通过使用 then 方法来生成 promise 链,我们就能轻易地处理异步时序依赖。并行执行多个异步任务也同样简单:仅使用 Promise.all 方法即可。
  • 通过将生成器和 promise 相结合我们能够使用同步代码来简化异步任务。