preface

Yu is the front end rookie, feeling the lack of posture level, difficult to view the front end of the overall situation. Then decided to follow the front of the knowledge of the context, with interest as the lead, supplemented by a few adhere to, hope in their own doubts to know, in the same way can help one or two, is not beautiful.

The code and documentation for this series are available here

Continue to bite the bullet… Love to hate asynchrony

Prior to the start

  • Synchronous and asynchronous

    function sync(){
      const doA = '12'
      const doB = '34'
    }
    function async(){
      ajax('/api/doC1', (res) => {
        doC2(res)
      })
    }
    Copy the code

    Synchronization is well understood. Tasks are executed one by one and doB can only be done after doA.

    An asynchronous task can be understood as having two phases, with the first phase of a doC making the request and the second phase processing at a future point after the request has finished.

    Both have their advantages and disadvantages. Synchronous tasks cause blocking, while asynchronous tasks require a mechanism to separate the two parts so that the host thread can continue to work within this interval without wasting time waiting.

    Take the browser as an example.

    The main thread calls the web API, makes the request through the worker thread, and then the main thread proceeds to other tasks (this is part1). After executing the asynchronous task, the worker thread registers the callback to the event queue, waits for the main thread to be idle, and fetches the callback to the main thread execution stack (this is part2).

  • Concurrency and parallelism

    Simple description: Concurrency is doing different things alternately, parallelism is doing different things at the same time.

    We can handle concurrency through multiple threads, but at the end of the day the CPU is just switching contexts quickly for fast processing. Parallelism is the use of multiple cores, processing multiple tasks at the same time.

  • Single thread and multi-thread

    We always say that JS is single-threaded and Node is single-threaded, but it’s not perfect. The so-called single thread refers to the JS engine to interpret and execute THE JS code thread is a, that is, we often say the main thread.

    For example, we are familiar with the node, I/O operations are actually completed through the thread pool, js-> call c++ function ->libuv method ->I/O operations performed -> after the completion of the js thread to continue to execute subsequent.

lesson1 Promise

callback

ajax('/a', (res) => { ajax('/b, (res) => { // ... })})Copy the code

Ugly callback form, no more to say

What’s your name

  • PromiseBorn in the community, it was originally an asynchronous programming solution, and then it was written into the language standard in ES6Promiseobject
  • Promise objects have two characteristics: the state is not affected by the outside world, and once the state changes, it will not change again

Basic usage

  • Promise is the constructor used to generate a Promise instance
    // Accept functions that take the resolve and reject methods
    const pr = new Promise((resolve, reject) = > {
      // do sth
      resolve(1) // pending -> resolved
      reject(new Error()) // pending -> rejected
    })
    Copy the code
  • The state change callback is passed in using the then method
    pr.then((value) = > {
      // onresolved cb
    }, (err) => {
      // onrejected cb
    })
    Copy the code

My stupid children

  • Promise.prototype.then

    In chained form, a new Promise is returned, and the return from the previous callback is passed as an argument to the next callback

  • Promise.prototype.catch

    Is actually an alias of. Then (Null, Rejection)

    Chain-writing is also supported, where the last catch can catch any uncaught error thrown by the previous Promise

  • Promise.all

    The parameters need to have an Iterator interface and return multiple Promise instances

    var p = Promise.all([p1, p2, p3]);
    Copy the code

    P1, P2, and p3 are all resolved before P is resolved, and P is reject.

    If there is an internal catch, the external catch does not catch an exception.

  • Promise.race

    // If not returned within 5 seconds, throw error
    const p = Promise.race([
      fetch('/resource-that-may-take-a-while'),
      new Promise(function (resolve, reject) {
        setTimeout((a)= > reject(new Error('request timeout')), 5000)})]); p.then(response= > console.log(response));
    p.catch(error= > console.log(error));
    Copy the code

    The first Promise to change state causes the p state to change.

  • Promise.resolve/reject

    Promise.resolve('1')
    Promise.resolve({ then: function() {
      console.log(123)}})Copy the code
    • Pass no parameters/non-Thenable objects, generating an immediate resolve Promise
    • Pass the Thenable object, execute the THEN method immediately, and then(normal Promise behavior) based on the state change
  • Promise.prototype.finally

    Promise.prototype.finally = function (callback) {
      let P = this.constructor;
      return this.then(
        value= > P.resolve(callback()).then((a)= > value),
        reason => P.resolve(callback()).then((a)= > { throw reason })
      );
    };
    Copy the code

    Will the final CB be executed anyway

Promise gives us an asynchronous alternative over the nested callback, but is actually implemented based on callbacks.

implementation

The simple Promise implementation code can be found here on Github

lesson2 Generator

A preliminary study

  • The basic concept

    function * gen() {
      const a = yield 1;
      return 2
    }
    const m = gen() // gen{<suspended>}
    m.next() // {value: 1, done: false}
    m.next() // {value: 2, done: true}
    m.next() // {value: undefined, done: true}
    m // gen {<closed>}
    Copy the code
    • Generator An iterator generating function, a state machine
    • Execution returns a traverser representing the internal pointer to the Generator function (in which case the yield expression is not evaluated)
    • Each call to the next method of the traverser executes the next pre-yield statement and returns one{ value, done }Object.
    • Among themvalueProperty represents the value of the current internal state, which is the value of the expression following the yield expression,doneProperty is a Boolean value indicating whether traversal is complete
    • If there is no yield, next executes until the end of the function and returns the result as value, or undefined if there is no return.
    • After that, the call to next returns{ value: undefined, done: true }, internal properties of the Generator[[GeneratorStatus]]Change to closed
  • yield

    • When the next method is called, the yield expression value is returned as value, and the next statement will be executed only when the next method is called. The effect of suspending execution is achieved, which is equivalent to having a lazy evaluation function
    • Without yield, the Generator function is a pure deferred function (next is called to execute)
    • Yield can only be used for Generator functions

methods

  • Generator.prototype.next()

    Adjust the subsequent behavior of the function by passing in parameters to inject different values inside the Generator function

    // This is done with parameters
    function* f() {
      for(var i = 0; true; i++) {
        var reset = yield i;
        if(reset) { i = - 1; }}}var g = f();
    g.next() // { value: 0, done: false }
    g.next() // { value: 1, done: false }
    // The argument passed will be assigned to I (the value of the expression after yield (I))
    // Then run var reset = I to reset
    g.next(true) // { value: 0, done: false }
    Copy the code
  • Generator.prototype.throw()

    • Objects returned by Generator functions have throw methods that throw errors outside the function and can be caught (only once) inside the function.
    • Arguments can be Error objects
    • If a try is not deployed inside the function… Catch code block, then throw error will be external try… Catch code block catch, if there is no external, the program error, interrupt execution
    • The throw method is followed by an internal catch with a next execution
    • An error inside a function can be caught externally
    • If there is no internal catch during the Generator execution, the execution will not continue, and the next call to next will be regarded as the completion of the Generator
  • Generator.prototype.return()

    • try ... finallyIf it exists, the return is executed after finally is executed, and the final result is the argument to the return method, after which the Generator finishes running and the next access is retrieved{value: undefined, done: true}
    • try ... finallyIf no, run return. The subsequent operations are the same as the previous one

All three methods let the Generator resume execution and replace the yield expression with a statement

yield*

  • It is useless to call directly from within one Generator to another. If you need to yield members of another Generator object within one Generator, use yield*

    function* inner() {
      yield 'a'
      // yield outer() // Returns an iterator object
      yield* outer() // Returns the internal value of an iterator object
      yield 'd'
    }
    function* outer() {
      yield 'b'
      yield 'c'
    }
    let s = inner()
    for (let i of s) {
      console.log(i)
    } // a b c d
    Copy the code
  • Yield * followed by an iterator object (all data structures that implement iterator can actually be yield* traversed)

  • If the propped Generator function has a return, the value of the return will be for… Of is ignored, so next does not return, but can actually return a value inside external Generetor as follows:

    function *foo() {
      yield 2;
      yield 3;
      return "foo";
    }
    function *bar() {
      yield 1;
      var v = yield *foo();
      console.log( "v: " + v );
      yield 4;
    }
    var it = bar();
    it.next()
    // {value: 1, done: false}
    it.next()
    // {value: 2, done: false}
    it.next()
    // {value: 3, done: false}
    it.next();
    // "v: foo"
    // {value: 4, done: false}
    it.next()
    // {value: undefined, done: true}
    Copy the code
  • An 🌰

    // Handle nested arrays
    function* Tree(tree){
      if(Array.isArray(tree)){
        for(let i=0; i<tree.length; i++) {yield* Tree(tree[i])
        }
      } else {
        yield tree
      }
    }
    let ss = [[1.2], [3.4.5].6[7]]
    for (let i of Tree(ss)) {
      console.log(i)
    } // 1 2 3 4 5 6 7
    // Understand for... Of is actually a while loop
    var it = iterateJobs(jobs);
    var res = it.next();
    while(! res.done){var result = res.value;
      // ...
      res = it.next();
    }
    Copy the code

Extra

  • A Generator function as a property of an object

    The writing style is very strange

    let obj = {
      * sss() {
        // ...}}let obj = ={
      sss: function* () {
        // ...}}Copy the code
  • This of the Generator function

    Generator returns an iterator object that inherits prototype’s methods, but since it does not return this, it returns:

    function* ss () {
      this.a = 1
    }
    let f = ss()
    f.a // undefined
    Copy the code

    Want an internal this binding traverser object?

    function * ss() {
      this.a = 1
      yield this.b = 2;
      yield this.c = 3;
    }
    let f = ss.call(ss.prototype)
    // f.__proto__ === ss.prototype
    f.next()
    f.next()
    f.a / / 1
    f.b / / 2
    f.c / / 3
    Copy the code

application

  • An 🌰

    // Take advantage of the paused state
    let clock = function* () {
      while(true) {
        console.log('tick')
        yield
        console.log('tock')
        yield}}Copy the code
  • Synchronous expression of asynchronous operations

    / / function Generator
    function* main() {
      var result = yield request("http://some.url");
      var resp = JSON.parse(result);
        console.log(resp.value);
    }
    // The ajax request function, in which the response is passed to the next method
    function request(url) {
      makeAjaxCall(url, function(response){
        it.next(response);
      });
    }
    The next method needs to be executed for the first time, returns the yield expression, triggers the asynchronous request, and jumps to the request function for execution
    var it = main();
    it.next();
    Copy the code
  • Control flow management

    / / synchronization steps
    let steps = [step1Func, step2Func, step3Func];
    function *iterateSteps(steps){
      for (var i=0; i< steps.length; i++){
        var step = steps[i];
        yieldstep(); }}// Asynchronous follow-up discussion
    Copy the code

implementation

TO BE CONTINUED

Asynchronous application of Lesson3 Generator

Back to asynchrony: Think of an asynchronous task as having two phases, the first phase executing now and the second phase executing in the future, where the task needs to be paused. The Generator seems to provide just such a moment when the pause ends and the second phase starts corresponding to the next call.

Imagine I have an asynchronous operation. I can pass in parameters for the operation through the Generator’s next method and then output the value of the returned value after the second phase. Maybe Generator can really be used as a container for asynchronous operations?

before it

Coroutines coroutine

Coroutine A execution -> Coroutine A is suspended, execution is transferred to coroutine B-> Execution is returned to A->A to resume execution

// yield is the split line between two asynchronous phases
function* asyncJob() {
  / /... Other code
  var f = yield readFile(fileA);
  / /... Other code
}
Copy the code

Thunk function

  • Evaluation strategy for parameters

    • The argument between a named call and a value call
    • The latter is simpler, but there may be cases where this parameter is not used and requires a lot of evaluation, resulting in a performance penalty
  • Thunk function in js

    • The traditional Thunk function is an implementation of a named call, in which a parameter is the return value of a temporary function and is evaluated where the parameter is needed
    • The Thunk function in JS is slightly different. The Thunk function in JS replaces a multi-argument function with a single-argument function.
      const Thunk = function(fn) {
        return function (. args) {
          return function (callback) {
            return fn.call(this. args, callback); }}; };Copy the code

      It looks like it’s just a new look, like it doesn’t work, right

Since the execution

Generator looks nice, but the next call looks cumbersome. How do you implement self-execution?

The Thunk function implements automatic execution of the Generator function

  • The Generator function executes automatically

    function* gen() {
      yield a // expression a
      yield 2
    }
    let g = gen()
    let res = g.next()
    while(! res.done) {console.log(res.value)
      res = g.next() // Expression b
    }
    Copy the code

    However, this is not suitable for asynchronous operations. If the previous step must be completed before the next step can be executed, the above automatic execution is not feasible.

    The next method is synchronous and must return the value immediately upon execution. Yield is fine for synchronous operations, but not for asynchronous ones. This is handled by returning a Thunk function or a Promise object. Value is the function/object, and done is still the rule.

    var g = gen();
    var r1 = g.next();
    // Pass a callback repeatedly
    r1.value(function (err, data) {
      if (err) throw err;
      var r2 = g.next(data);
      r2.value(function (err, data) {
        if (err) throw err;
        g.next(data);
      });
    });
    Copy the code
  • Automatic process management of the Thunk function

    • Ideas:

      Yield in the Generator asynchronously transfers control to the Thunk function by yield, and then returns control to the Generator by calling Generator’s next method in the Thunk function’s callback. At this point, the asynchronous operation ensures completion and starts the next task.

      The Generator is a container for asynchronous operations, and the implementation of automatic execution requires a mechanism in which control is switched back automatically after the result of the asynchronous operation, which is the point in time when the callback function is executed.

      // Executor of the Generator function
      function run(fn) {
        let gen = fn()
        // Callback to Thunk
        function cb(err, data) {
          // Give control to the Generator to get the next yield expression (asynchronous task)
          let result = gen.next(data)
          // No more tasks, return
          if (result.done) return
          // Give control to the Thunk function, passing in the callback
          result.value(cb)
        }
        cb()
      }
      / / function Generator
      function* g() {
        let f1 = yield readFileThunk('/a')
        let f2 = yield readFileThunk('/b')
        let f3 = yield readFileThunk('/c')}// Thunk function readFileThunk
      const Thunk = function(fn) {
        return function (. args) {
          return function (callback) {
            return fn.call(this. args, callback); }}; };var readFileThunk = Thunk(fs.readFile);
      readFileThunk(fileA)(callback);
      // Automatic execution
      run(g)
      Copy the code

The famous CO

  • instructions

    • Instead of writing the actuators described above, the CO module simply wraps two automatic Generator actuators based on Thunk functions and Promise objects into a single module
    • Use condition: Only Thunk functions or Promise objects or an array of Promise objects can be used after yield
  • Promise based executor

    function run(fn) {
      let gen = fn()
      function cb(data) {
        // Pass the data returned from the previous task as an argument to the next method and pass control back to Generator
        // The result variable refers to the {value, done} object
        // Not to be confused with 'let result = yield XXX' in Generator
        let result = gen.next(data)
        if (result.done) return result.value
        result.value.then(function(data){
          // Resolved to execute CB (data)
          // Start the next loop for automatic execution
          cb(data)
        })
      }
      cb()
    }
    Copy the code
  • Source code analysis

    This is similar to the implementation above

    function co(gen) {
      var ctx = this;
      var args = slice.call(arguments.1) // All arguments except the first one
      // Return a Promise object
      return new Promise(function(resolve, reject) {
        // If it is a Generator, get the traverser object gen
        if (typeof gen === 'function') gen = gen.apply(ctx, args);
        if(! gen ||typeofgen.next ! = ='function') return resolve(gen);
        // Execute the next method of the traverser object gen for the first time to get the first task
        onFulfilled();
        Resolved will be called later after each asynchronous task has completed and control will be handed back to the Generator
        function onFulfilled(res) {
          var ret;
          try {
            ret = gen.next(res); // Get the {value,done} object, where control is temporarily given to the asynchronous task to execute the asynchronous task after yield
          } catch (e) {
            return reject(e);
          }
          next(ret); // Enter the next method
        }
        // The same can be done
        function onRejected(err) {
          var ret;
          try {
            ret = gen.throw(err);
          } catch (e) {
            return reject(e);
          }
          next(ret);
        }
        / / key
        function next(ret) {
          // Set Resolved after traversing the asynchronous task and return the last value
          if (ret.done) return resolve(ret.value);
          // Get the next asynchronous task and turn it into a Promise object
          var value = toPromise.call(ctx, ret.value);
          // The ondepressing method will be called after the asynchronous task is completed (here the callback parameters for the asynchronous task after yield are set for then).
          if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
          return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
            + 'but the following object was passed: "' + String(ret.value) + '"')); }})}Copy the code

    Again, specify a callback function for the Promise object then method, trigger the callback when the asynchronous task is complete, execute Generator’s next method within the callback function, and proceed to the next asynchronous task for automatic execution.

    An 🌰

    'use strict';
    const fs = require('fs');
    const co =require('co');
    function read(filename) {
      return new Promise(function(resolve, reject) {
        fs.readFile(filename, 'utf8'.function(err, res) {
          if (err) {
            return reject(err);
          }
          return resolve(res);
        });
      });
    }
    co(function* () {
      return yield read('./a.js');
    }).then(function(res){
      console.log(res);
    });
    Copy the code

Lesson4 async function

Syntactic sugar

  • To compare

    function* asyncReadFile () {
      const f1 = yield readFile('/etc/fstab');
      const f2 = yield readFile('/etc/shells');
      console.log(f1.toString());
      console.log(f2.toString());
    };
    const asyncReadFile = async function () {
      const f1 = await readFile('/etc/fstab');
      const f2 = await readFile('/etc/shells');
      console.log(f1.toString());
      console.log(f2.toString());
    };
    Copy the code

    It looks like it’s just a substitution, but there’s a difference

    • Async functions have built-in actuators, so there is no need to manually execute the next method and introduce co module
    • Async is more broadly applicable. The CO module strictly limits yield to Thunk functions or Promise objects, while await can be Promise objects or primitive type values
    • Return Promise, which is a little bit like CO
  • usage

    • Async indicates asynchronous operations within a function
    • Since async functions return promises, you can use async functions as arguments to await commands
    • Async functions can be used in many scenarios applicable to functions and methods

grammar

  • Return to the Promise of

    • Async functions will not change the state of the returned Promise object until all await promises have been executed (except for return or error) i.e. the then method will only be executed after the internal operation has completed
    • The value of the return inside the async function is used as a callback to the then method of the returned Promise
    • An error thrown inside the async function causes the returned Promise to become the Rejected state and the error is caught by a catch
  • The async command and the Promise that follows

    • If the async command is not a Promise object, it will be converted to a Resolved Promise
    • If the async Promise after the async command changes to the Rejected state or directly rejected, the async function is interrupted and the error can be caught by the callback function of the then method
    • If you want an await Promise from async not to affect another await Promise, you can put the await Promise in a try… In the catch block, so that the following will still execute normally, you can also put multiple await promises in a try… In addition to the catch block, error retries can be added

Use attention

  • Asynchronous tasks that are independent of each other can be modified to run concurrently (promise.all)

    let [foo, bar] = await Promise.all([getFoo(), getBar()]);
    Copy the code
  • Await with the for… of

    I think it’s still in the proposal stage

    for await (const item of list) {
      console.log(item)
    }
    Copy the code

implementation

  • This encapsulates the executor and Generator functions, as described in the previous lesson

As for 🌰

  • Concurrent requests, sequential output
    async function logInOrder(urls) {
      // Read the remote URL concurrently
      const textPromises = urls.map(async url => {
        const response = await fetch(url);
        return response.text();
      });
      // Output in order
      for (const textPromise of textPromises) {
        console.log(awaittextPromise); }}Copy the code

Promise is the dominant asynchronous solution, Generator as a container with async await syntactic sugar provides what may seem like a more elegant way to write, but in reality because everything is a Promise, synchronous tasks are also wrapped as asynchronous tasks to execute. My personal feeling is still inadequate.

Although published here, but after all, it is a person’s words, but also a daily study of the notes, the content may not be full, see the official gentlemen also hope to contain.