Promise series

  1. Promise – Three questions of the soul
  2. Promise – Handwritten source code
  3. Promise – Fully functional

There are a lot of problems with promises, and internally we still use callbacks, and if there are too many callbacks, we’re still going to have callback hell how do we fix that? We want asynchronous methods to be written more like synchronous. Today we will explore the simple principles of generator, CO, async, await

The generator using

We first implement a basic generator function based on fixed syntax

function* gen() {yield 1
  yield 2
}
const g = gen()
console.log(g.next()) // {value: 1,done: false}
console.log(g.next()) // {value: 2,done: false}
console.log(g.next()) // {value: undefined,done: true}
Copy the code
  • Function *: This defines a generation function that returns a Generator object
  • Yield: Used to pause or resume a generator function, that is, we call next several times, separated by this parameter
  • The yield* expression is used to delegate to another generator or iterable. If there is this flag, the other one will not go all the way
function* tests() {
  yield 1
  yield 2
}

function* test() {
  yield* tests() // When yieldf* is also a generator function, method pauses and replies are nested
  yield 3
}
const t = test()
console.log(t.next()) // { value: 1, done: false }
console.log(t.next()) // { value: 2, done: false }
console.log(t.next()) // { value: 3, done: false }
console.log(t.next()) // { value: undefined, done: true }
Copy the code

Now that you understand the basic usage rules, let’s look at some of the operating principles of the Generator

Operation Principle of generator

The generator operates as a switch + pointer. Now let’s implement the first code in js in a simpler way

  • First we know that when we call the generator function, we return an object with a next method, and when we call the next method, we return a {value:xx,done:false} object
function gen() {
  return {
    next() {
      return {
        value: ' '.done: false,}},}}Copy the code
  • Of course, value and done are not dead variables, so we need to find a way to implement this parameter change. From the beginning we know that Pointers are the way to implement the generator, so let’s set Pointers first
// Here through the closure of the way, to achieve the gen function internal variables a save and protection
function gen() {
  const content {
    next: 0.// Set the next pointer variable
    done: flase, // Whether the iteration is complete}... }Copy the code
  • Now that we have Pointers, let’s move on to our great goal of returning different arguments when we keep calling next.
// We use a function to achieve our operation
function _gen(content) {
  // By receiving the next pointer in the content, we know what to do next
  switch (content.next) {
    case 0:
      content.next = 1
      return 1
    case 1:
      content.next = 2
      return 2
    case 2:
      content.done = true
      return undefined}}Copy the code
  • With the step decomposition method, we can omit the return value of our next method
// ...
next() {
  value: _gen(content),
  done: content.done
}
// ...
Copy the code

At this point we have basically completed the simple implementation of the first section of the native Generator method. At the same time, we compared the core code of Babel transformation. Although it is more formal, we added the previous pointer to encapsulate the done = false method, but the actual core is still pointer + switch.

switch ((_context.prev = _context.next)) {
  case 0:
    _context.next = 2
    return 1

  case 2:
    _context.next = 4
    return 2

  case 4:
  case 'end':
    return _context.stop()
}
Copy the code

Now let’s perfect our own way of writing

function gen() {
  const content = {
    prev: 0.next: 0.done: false.stop: () = > {
      this.done = true}},return {
    next() {
      return {
        value: _gen(content),
        done: content.done,
      }
    },
  }
}
function _gen(content) {
  switch ((content.prev = content.next)) {
    case 0:
      content.next = 1
      return 1
    case 1:
      content.next = 2
      return 2
    case 2:
      content.stop()
      return undefined}}Copy the code

Now that we have a general understanding of the generator, there is a problem. We are trying to create a lot of generators

Of course not. Let’s dive into the mysterious world of Genertor and see what it can bring to our code

generator + Promise

I’m sure we know from the title that we’re going to implement asynchrony with generator + Promise but how do we do that? In general, we might write something like this

const fs = require('fs').promises
function* getData() {
  let path1 = yield fs.readFile('./path.txt'.'utf-8')
  let name = yield fs.readFile(path1, 'utf-8')
  return name
}
const _fs = getData()
_fs
  .next()
  .value.then((rs) = > {
    console.log(rs)
    _fs
      .next(rs)
      .value.then((rs) = > {
        console.log(rs)
      })
      .catch((error) = > {
        console.log(error)
      })
  })
  .catch((error) = > {
    console.log(error)
  })
Copy the code

But that doesn’t seem to accomplish our goal for today, and it just makes the Promise more complicated

Co library

Let’s recall that our goal is to make promises as complex as possible as we want them to be written synchronously. Let’s look at the getDate method above

function* getData() {
  let path1 = yield fs.readFile('./path.txt'.'utf-8')
  let name = yield fs.readFile(path1, 'utf-8')
  return name
}
Copy the code

Is that close to what we want, so what we should do is, instead of having to write a bunch of Promise handlers ourselves, figure out how to automate this method. The CO library has given me the solution, let’s briefly look at how it is implemented.

function co(it) {
  // We finally return a Promise
  return new Promise((resolve, reject) = > {
    // Loop back until generator finally done is false and can no longer iterate
    function step(data) {
      // Iterate once to get the content of the current step
      let { value, done } = it.next(data)
      if(! done) {Resolve (); // The first step returns the value of the first call.
        Promise.resolve(value).then((data) = > {
          step(data)
        }, reject)
      } else {
        // When all the iterations are finished, the unified return exits
        resolve(value)
      }
    }
    step()
  })
}
Copy the code

Let’s use the co() method to modify the read file example

function* getData() {
  let path1 = yield fs.readFile('./path.txt'.'utf-8')
  let name = yield fs.readFile(path1, 'utf-8')
  return name
}
co(getData).then((data) = > {
  console.log(data) / / fair - TAO
})
Copy the code

Oh oh oh oh oh oh ~~~~

Let’s take a look at this piece of code and see if it suddenly feels familiar. Yes, you are right, it is async + await

async + await

Async + await === generator + co

async function getData() {
  let path1 = await fs.readFile('./path.txt'.'utf-8')
  let name = await fs.readFile(path1, 'utf-8')
  return name
}
getData().then((data) = > {
  console.log(data)
})
/ / fair - TAO
Copy the code