conclusion

  • 1. The execution of async functions is exactly the same as ordinary functions, as long as one line, without calling the next method
  • 2, await the original type value will automatically become an immediate Resolved Promise object
  • 3,asyncFunction of the internalreturnThe value returned by the statement becomesthenMethod to call the functionparameter
  • 4,asyncFunction returnedPromiseObject,Have to wait untilinternalAll awaitThe commandbehindthePromise objectafterFor the state change to occur,unlessencounterreturnStatements orThrow an error
  • 5. Any Promise object after an await statement becomes a reject stateasyncThe function breaks execution
  • 6, it is better to await the “await” command in the try… In the catch block
  • 7, async operation after multiple await commands, if there is no await relation, better let them fire simultaneously,await promise.all ([getFoo(), getBar()]))

meaning

1.asyncFunction isGeneratorFunction syntax sugar

asyncThe function is to takeGeneratorFunction asterisk (*) replaceasyncThat will beyieldreplaceawaitThat’s all

Using the Generator function, read two files in turn:

const fs = require('fs');

const readFile = function (fileName) {
  return new Promise(function (resolve, reject) {
    fs.readFile(fileName, function(error, data) {
      if (error) return reject(error);
      resolve(data);
    });
  });
};

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

The function gen in the above code can be written as async

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

Async functions passAt four o 'clockImprovements to the Generator function

(1) Built-in actuators

Async functions are executed just like normal functions, with only one line, without calling the next method or using the CO module

Generator functions must be executed by an executor, hence the CO module, while async functions have their own executor. In other words, async functions are executed exactly like normal functions, with only one line.

No need to call the next method or use the CO module

const fs = require('fs');

const readFile = function (fileName) {
  return new Promise(function (resolve, reject) {
    fs.readFile(fileName, function(error, data) {
      if (error) return reject(error);
      resolve(data);
    });
  });
};

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

//async function execution
asyncReadFile();
Copy the code

(2) Better semantics

Async and await are semantic clearer than asterisks and yield.

asyncThere are asynchronous operations in the function
awaitRepresentation: The following expression needs to wait for the result

(3) wider applicability

By convention of the CO module, a yield command can only be followed by a Thunk function or a Promise object.

asyncFunction of theawaitAfter the command, it can bePromiseThe objects andA value of the original typeValues, strings, and Booleans, but at this pointAutomatically becomes an immediate Resolved Promise object)
awaitThe value of the original type is automatically converted to immediatelyresolvedthePromise Object (Resolved (Original value itself))

(4) Return the Promise

asyncThe return value of the function isPromise Object, this is more thanGeneratorThe return value of the function isIteratorObjects are much more convenient. You can usethenMethod specifies what to do next
asyncThe function can be completely viewed asMultiple asynchronous operations, packaged into aPromise Object, andawaitThe command is the insidethenThe commandThe syntactic sugar

Basic usage

1.asyncfunctionreturnaPromise Object,canmakeBy thenmethodsAdd a callback function

2, whenFunction performsWhen, once encounteredawaitI’m going to go back first,Etc.toAsynchronous completionAgain,Then performThe statement following the function body

async function getStockPriceByName(name) {
  const symbol = await getStockSymbol(name);
  const stockPrice = await getStockPrice(symbol);
  return stockPrice;
}

getStockPriceByName('goog').then(function (result) {
  console.log(result);  / / is stockPrice
});
Copy the code

The async keyword before the above code function indicates that there are asynchronous operations inside the function. When this function is called, a Promise object is immediately returned, so you can use then to listen for the completion of the function.

function timeout(ms) {
  return new Promise((resolve) = > {
    setTimeout(resolve, ms);
  });
}

async function asyncPrint(value, ms) {
  await timeout(ms);
  console.log(value);
}

asyncPrint('hello world'.5000);// Output 'hello world' after 5s

// Change it up a bit...
async function asyncPrint(value, ms) {
  await timeout(ms);
  return value;
}

asyncPrint('hello world'.5000).then((result) = >{
    console.log(result);
});// Output 'hello world' after 5s
Copy the code

Due to theasyncThe function returns zeroPromiseObject that can be used asawaitCommand parameters

So, the above example could also be written as follows:

// Change timeout to async as well
async function timeout(ms) {
  await new Promise((resolve) = > {
    setTimeout(resolve, ms);
  });
}

async function asyncPrint(value, ms) {
  await timeout(ms);
  console.log(value);
}

asyncPrint('hello world'.50);
Copy the code

Async functions can be used in many ways:

Function declaration

Functional expression

Object methods

The method of the Class

Arrow function

// Function declaration
async function foo() {}

// Function expression
const foo = async function () {};

// Object method
let obj = { async foo(){}}; obj.foo().then(...)// Class method
class Storage {
  constructor() {
    this.cachePromise = caches.open('avatars');
  }

  async getAvatar(name) {
    const cache = await this.cachePromise;
    return cache.match(`/avatars/${name}.jpg`); }}const storage = new Storage();
storage.getAvatar('jake'). Then (...). ;// Arrow function
const foo = async() = > {};Copy the code

grammar

Return a Promise object

The async function returns a Promise object.

asyncFunction of the internalreturnThe value returned by the statement becomesthenMethod to call the functionparameter

async function f() {
  return 'hello world';
}

Function f returns a value that is received by the then callback.
f().then(v= > console.log(v))
// "hello world"
Copy the code

asyncThe function throws an error internally, which will result in a returnPromiseObject torejectState, where the thrown error object is received by the catch method callback

async function f() {
  throw new Error('Wrong');
}

f().then(
  v= > console.log(v),
  e= > console.log(e,'e'))// Error: Error,'e'
Copy the code

The state change of the Promise object

1.asyncFunction returnedPromiseObject,Have to wait untilinternalAll awaitThe commandbehindthePromise objectafterFor the state change to occur,unlessencounterreturnStatements orThrow an error

2, onlyasyncThe asynchronous operation inside the function is executed only after it has been executedthenMethod specifies the callback function

async function getTitle(url) {
  let response = await fetch(url);
  let html = await response.text();
  return html.match(/<title>([\s\S]+)<\/title>/i) [1];
}
getTitle('https://tc39.github.io/ecma262/').then(console.log)
// "ECMAScript 2017 Language Specification"
Copy the code

The getTitle function has three internal operations: fetch the page, retrieve the text, and match the page title. The console.log in the then method is executed only when all three operations are complete.

Await orders

1.awaitThe command is followed by onePromiseObject that returns the result of that object

2. If notPromise Object directly returns the corresponding value

async function f() {
  / / is equivalent to
  // return 123;
  return await 123;
}

f().then(v= > console.log(v))
Copy the code

3. After the await command is a thenable object (The object that defines the then method), thenawaitIt’s going to be equal toPromiseobject

class Sleep {
  constructor(timeout) {
    this.timeout = timeout;
  }
  then(resolve, reject) {
    const startTime = Date.now();
    setTimeout(
      () = > resolve(Date.now() - startTime),
      this.timeout ); }} (async() = > {const actualTime = await new Sleep(1000);
  console.log(actualTime); }) ();Copy the code

In the code above, the await command is followed by an instance of the Sleep object. This instance is not a Promise object, but because the then method is defined, await will treat it as a Promise.

4. Await the Promise object if it becomes rejectrejectThe parameter will becatchMethod received by the callback function

async function f() {
  await Promise.reject('Wrong');
}

f()
.then(v= > console.log(v))
.catch(e= > console.log(e,'e'))
// Error,'e'

Copy the code

In this code, the await statement is preceded by no return, but the reject argument is still passed to the catch callback.

Here the effect is the same if return is preceded by await.

async function f() {
  return await Promise.reject('Wrong');
}

f()
.then(v= > console.log(v))
.catch(e= > console.log(e,'e'))
// Error,'e'

Copy the code

5. Any Promise object after an await statement becomes a reject stateasyncThe function breaks execution

async function f() {
  await Promise.reject('Wrong');
  await Promise.resolve('hello world'); // Will not be executed
}
Copy the code

6. Do not interrupt subsequent asynchronous operations even if the previous one fails

(1) the try… catch

We can put the first await in a try… Inside the catch structure, so that the second await is executed regardless of whether the asynchronous operation succeeds or not.

async function f() {
  try {
    await Promise.reject('Wrong');
  } catch(e) {
  }
  return await Promise.resolve('hello world');
}

f()
.then(v= > console.log(v))
// hello world
Copy the code
(2) await the Promise followed by a catch method
async function f() {
  await Promise.reject('Wrong')
    .catch(e= > console.log(e));
  return await Promise.resolve('hello world');
}

f()
.then(v= > console.log(v))
/ / make a mistake
// hello world
Copy the code

Error handling

1, ifawaitThe following asynchronous operation error, then equal toasyncFunction returnedPromiseThe object isreject

sync function f() {
  await new Promise(function (resolve, reject) {
    throw new Error('Wrong');
  });
}

f()
.then(v= > console.log(v))
.catch(e= > console.log(e,'e'))
// Error: Error,'e'
Copy the code

2. The way to prevent mistakes is to put it intry... catchInside the code block

async function f() {
  try {
    await new Promise(function (resolve, reject) {
      throw new Error('Wrong');
    });
  } catch(e) {
  }
  return await('hello world');
}
Copy the code

3, if there are multipleawaitCommands can be placed uniformlytry... catchIn the structure

async function main() {
  try {
    const val1 = await firstStep();
    const val2 = await secondStep(val1);
    const val3 = await thirdStep(val1, val2);

    console.log('Final: ', val3);
  }
  catch (err) {
    console.error(err); }}Copy the code

4, the use oftry... catchStructure, implementationRepeated attempts

// If the await operation succeeds, a break statement is used to exit the loop; If it fails, it is caught by a catch statement and the next loop is passed.
const superagent = require('superagent');
const NUM_RETRIES = 3;

async function test() {
  let i;
  for (i = 0; i < NUM_RETRIES; ++i) {
    try {
      await superagent.get('http://google.com/this-throws-an-error');
      break;
    } catch(err) {}
  }
  console.log(i); / / 3
}

test();
Copy the code

Use caution points

1, it is better to put await command in try… In the catch block

Await the Promise object after the await command, and the result may be rejected, so it is better to await the await command in the try… In the catch block.

async function myFunction() {
  try {
    await somethingThatReturnsAPromise();
  } catch (err) {
    console.log(err); }}// Another way to write it

async function myFunction() {
  await somethingThatReturnsAPromise()
  .catch(function (err) {
    console.log(err);
  });
}
Copy the code

2, async operation after multiple await commands, if there is no await relation, better let them fire simultaneously,await promise.all ([getFoo(), getBar()]))

let foo = await getFoo();
let bar = await getBar();
Copy the code

GetFoo and getBar are two independent asynchronous (that is, non-dependent) operations written as a secondary relationship. This is time consuming because getBar will not be executed until getFoo is done, and they can be fired at the same time.

/ / write one
let [foo, bar] = await Promise.all([getFoo(), getBar()]);

/ / write two
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;
Copy the code

3. Await commands can only be used in async functions

If you use a normal function, you get an error.

async function dbFuc(db) {
  let docs = [{}, {}, {}];

  / / an error
  docs.forEach(function (doc) {
    await db.post(doc);
  });
}
Copy the code
If you haveforEachThe parameter of the method is changed toasyncDelta function, there’s also a problem
function dbFuc(db) { // Async is not required here
  let docs = [{}, {}, {}];

  // May get an error result
  docs.forEach(async function (doc) {
    await db.post(doc);
  });
}
Copy the code

The above code may not work because the three db.POST operations will be executed concurrently, that is, at the same time, rather than secondarily.

The correct way to write it is to useforcycle
async function dbFuc(db) {
  let docs = [{}, {}, {}];

  for (let doc of docs) {
    awaitdb.post(doc); }}Copy the code
Multiple requests are executed concurrently, using the promise.all method

When all three requests are resolved, these two options will have the same effect.

async function dbFuc(db) {
  let docs = [{}, {}, {}];
  let promises = docs.map((doc) = > db.post(doc));

  let results = await Promise.all(promises);
  console.log(results);
}

// Or use the following notation

async function dbFuc(db) {
  let docs = [{}, {}, {}];
  let promises = docs.map((doc) = > db.post(doc));

  let results = [];
  for (let promise of promises) {
    results.push(await promise);
  }
  console.log(results);
}
Copy the code
esmThe module loader supports the top layerawait, i.e.,awaitThe command may not be placedasyncIn the function, just use
// async
const start = async() = > {const res = await fetch('google.com');
  return res.text();
};

start().then(console.log);

// top-level await
const res = await fetch('google.com');
console.log(await res.text());
// The second script must use the ESM loader to take effect.
Copy the code

4. Async functions can preserve the run stack

const a = () = > {
  b().then(() = > c());
};
Copy the code

In the code above:

  • functionaAn asynchronous task runs internallyb().
  • whenb()At run time, the functiona()Execution continues without interruption.
  • Wait until theb()Run done, maybea()It was over a long time ago,b()The context is gone.
  • ifb()orc()Error, error stack willDo not include (a).

Change the above example to async

const a = async() = > {await b();
  c();
};
Copy the code

In the code above, when b() runs, a() is paused, and the context is saved. If b() or c() reports an error, the error stack will include a().

The implementation principle of async function

async And the way that functions are implemented is that the GeneratorFunctions andautoactuator, wrapped in a function

async function fn(args) {
  // ...
}

/ / is equivalent to

function fn(args) {
  return spawn(function* () {
    // ...
  });
}
Copy the code

All async functions can be written in the second form above, where the spawn function is the auto-executor.

Here’s an implementation of the spawn function, which is basically a copy of the autoexecutor above.

function spawn(genF) {
  return new Promise(function(resolve, reject) {
    const gen = genF();
    function step(nextF) {
      let next;
      try {
        next = nextF();
      } catch(e) {
        return reject(e);
      }
      if(next.done) {
        return resolve(next.value);
      }
      Promise.resolve(next.value).then(function(v) {
        step(function() { return gen.next(v); });
      }, function(e) {
        step(function() { return gen.throw(e); });
      });
    }
    step(function() { return gen.next(undefined); });
  });
}

Copy the code

Comparison with other asynchronous processing methods

Example: Suppose a series of animations are deployed on a DOM element, and the first animation ends before the next one begins. If one of the animations fails, it does not proceed further and returns the return value of the last successfully executed animation.

Written Promise

function chainAnimationsPromise(elem, animations) {

  // The ret variable is used to hold the return value of the previous animation
  let ret = null;

  // Create an empty Promise
  let p = Promise.resolve();

  // Add all animations using the then method
  for(let anim of animations) {
    p = p.then(function(val) {
      ret = val;
      return anim(elem);
    });
  }

  // Return a Promise with an error-catching mechanism deployed
  return p.catch(function(e) {
    /* Ignore the error and continue with */
  }).then(function() {
    return ret;
  });

}
Copy the code

Writing of a Generator function

function chainAnimationsGenerator(elem, animations) {

  return spawn(function* () {
    let ret = null;
    try {
      for(let anim of animations) {
        ret = yieldanim(elem); }}catch(e) {
      /* Ignore the error and continue with */
    }
    return ret;
  });

}
Copy the code

The problem with this is that you must have a task runner that automatically executes the Generator. The spawn function of the above code is the spawn function that returns a Promise object, and the expression following the yield statement must return a Promise.

Async function writing method

async function chainAnimationsAsync(elem, animations) {
  let ret = null;
  try {
    for(let anim of animations) {
      ret = awaitanim(elem); }}catch(e) {
    /* Ignore the error and continue with */
  }
  return ret;
}
Copy the code

Example: Complete asynchronous operations sequentially

Read a set of urls remotely in turn and output the results in the order they were read.

Written Promise

function logInOrder(urls) {
  // Read all urls remotely
  const textPromises = urls.map(url= > {
    return fetch(url).then(response= > response.text());
  });

  // Output in order
  textPromises.reduce((chain, textPromise) = > {
    return chain.then(() = > textPromise)
      .then(text= > console.log(text));
  }, Promise.resolve());
}
Copy the code

The code above uses the fetch method to read a set of urls remotely simultaneously. Each fetch operation returns a Promise object into the textPromises array. The Reduce method then processes each Promise object in turn, and then uses then to concatenate all the Promise objects so that the results can be output in turn.

The async function implements concurrency

Note: OnlyasyncThe internal execution of a function is secondary, and the external execution is unaffected

async function logInOrder(urls) {
  for (const url of urls) {
    const response = await fetch(url);
    console.log(awaitresponse.text()); }}Copy the code

The code problem is: all remote operations are secondary. Only when the previous URL returns a result will the next URL be read, which is inefficient and a waste of time.

What we need is to make remote requests concurrently.

Making remote requests concurrently: each async function is independent of each other (and the parent async function)

async function logInOrder(urls) {
    // Read the remote URL concurrently
    // Each async function is independent of each other (and not affected by the parent async function)
    const textPromises = urls.map(async url => {
        const response = await fetch(url);
        return response.text();
    });

    // Output in order
    // Output in order with await in parent async function
    for (const textPromise of textPromises) {
        console.log(awaittextPromise); }}Copy the code

Although the map method takes an async function as an argument, it is executed concurrently because only the async function internally is executed secondary and the outside is unaffected. Behind the for… The of loop uses await inside, so it outputs sequentially.