There are always many helpless in life as long as you persevere, you will find that you will be more helpless. The author hope: whether you are being cut leek, hope you lie to win!

ES2015 is short for ECMAScript 2015, which in turn is the European Computer Manufacturers Association. ECMAScript stands for the specification that the language JavaScript follows. After ECMAScript 5.1 was released in 2011, work began on version 6.0, and it wasn't until 2015 that ES6.0 was officially released. Therefore, the original meaning of the word ES6 refers to ES6.0, the next version of the JavaScript language after ES5.1.Copy the code

In-depth study of ES6 apis

Symbol

ES6 introduces a new primitive data type, Symbol, that represents unique values. It is the seventh data type in JavaScript, following undefined, NULL, Boolean, String, Number, and ObjectCopy the code
1. Direct callSymbolThe function generates a SymbolSymbolCannot be used before a functionnewCommand, otherwise an error will be reported.



2.SymbolThe function can take a string as an argument representing a description of Symbol, mainly for display on the console or for easy differentiation when converted to a string.

let s1 = Symbol('foo');
let s2 = Symbol('bar');

s1 // Symbol(foo)
s2 // Symbol(bar)
Copy the code

Pay attention to,SymbolThe argument to the function only represents the description of the current Symbol value, so the same argumentSymbolThe return values of the function are not equal.

// No arguments
let s1 = Symbol(a);let s2 = Symbol(a); s1 === s2// false

// With parameters
let s1 = Symbol('foo');
let s2 = Symbol('foo');

s1 === s2 // false
Copy the code
3.SymbolValues can be used as identifiers for the property names of objects, as per eachSymbolThe values are not equal. This means that you are guaranteed not to have a property with the same name, preventing a key from being accidentally overwritten or overwritten.

Note that inside an object, when defining an attribute using the Symbol value, the Symbol value must be placed in square brackets.

let mySymbol = Symbol(a);// The first way
let a = {};
a[mySymbol] = 'Hello! ';

// The second way
let a = {
  [mySymbol]: 'Hello! '
};

// The third way
let a = {};
Object.defineProperty(a, mySymbol, { value: 'Hello! ' });

// All the above methods give the same result
a[mySymbol] // "Hello!"
Copy the code
Tip: Symbol is the attribute name, which does not appear in thefor... in,for... ofIt’s not in the loopObject.keys(),Object.getOwnPropertyNames(),JSON.stringify()To return. But there is oneObject.getOwnPropertySymbolsMethod to get all Symbol attribute names for the specified object.

Symbol.for

Sometimes, we want to reuse the same Symbol value. The symbol.for method can do this. It takes a string as an argument and searches for a Symbol value named with that parameter. If so, it returns the Symbol value. Otherwise, it creates and returns a Symbol value named with that string.Copy the code
let s1 = Symbol.for('foo');
let s2 = Symbol.for('foo');
s1 === s2 // true
Copy the code

Built-in Symbol

In addition to defining the Symbol values you use, ES6 provides 11 built-in Symbol values that point to methods used within the language.Copy the code
Symbol.hasInstance
Symbol.isConcatSpreadable
Symbol.species
Symbol.match
Symbol.replace
Symbol.search
Symbol.split
Symbol.toPrimitive
Symbol.toStringTag
Symbol.unscopables
Symbol.iteratorSymbol of the object.iteratorProperty that points to the default generated traverser method for the object.Copy the code

The Set and WeakSet

ES6 provides a new data structure, Set. It is similar to an array, but the values of the members are unique and there are no duplicate values. You can use Set when you need to record different members and you don't want to duplicate recordsCopy the code

How to generate a set

let set1 = new Set(a)let set2 = new Set([1.2.3])
Copy the code

Set instance attributes:

 Set. Prototype. Size: returnSetThe total number of members of an instance.Copy the code

The methods of a Set instance fall into two broad categories: operation methods (for manipulating data) and traversal methods (for traversing members).

Four operation methods:

- Set.prototype.add(value) : Adds a value, returnsSetThe structure itself. -Set.prototype.delete(value) : Deletes a value and returns a Boolean value indicating whether the deletion was successful. -Set.prototype.has(value) : Returns a Boolean value indicating whether the value isSetA member of. -Set.prototype.clear() : Clears all members, no return value.Copy the code

Four traversal methods:

- Set.prototype.keys() : returns key name traverser -Set.prototype.values() : returns key value traverser -Set.prototype.entries() : Returns key value pair traversal -Set.prototype.foreach () : Use the callback function to iterate over each memberCopy the code

Note: The key and value in a Set instance are the same, sokeys()andvalues()The result of both methods is the same

WeakSet structure is similar to Set, which is also a collection of non-repeating values. However, it differs from Set in two ways.

  • WeakSet members can only be objects, not other types of values.

  • Objects in a WeakSet are weak references

If an object has no references, the object is garbage collected as soon as possible, freeing up its memory.

That is, garbage collection mechanism does not consider WeakSet’s reference to the object, that is, if other objects no longer reference the object, garbage collection mechanism will automatically recover the memory occupied by the object, not considering the object still exists in WeakSet.

WeakSet structure has the following three methods.

  • WeakSet.prototype.add(value): Adds a new member to the WeakSet instance.
  • WeakSet.prototype.delete(value): Clears the specified member of the WeakSet instance.
  • WeakSet.prototype.has(value): Returns a Boolean value indicating whether a value is in WeakSetIn an instance.

WeakSet cannot be traversed, because members are weak references and may disappear at any time.

Example:

let div = document.querySelector('div')
let set = new Set()
set.add(div)
/ /... some code
document.body.removeChild(div)
div = null // The DOM object is still in memory because it is still referenced in the Set
Copy the code
let div = document.querySelector('div')
let weakset = new WeakSet()
weakset.add(div)
/ /... some code
document.body.removeChild(div)
div = null // The DOM object has no reference and will be collected by garbage collection
Copy the code

Map and WeakMap

Map

JavaScript objects are essentially collections of key-value pairs (Hash structures), but traditionally strings can only be used as keys. This puts a great limit on its use.

To solve this problem, ES6 provides Map data structures. It is a collection of key-value pairs similar to objects, but the scope of “keys” is not limited to strings. Values of all types (including objects) can be used as keys, which is a more complete implementation of Hash structures.

Generate Map instance:

const map1 = new Map(a);const map2 = new Map([['name'.'Joe'],
  ['title'.'Author']]);Copy the code

Map instance attributes:

  • Map.prototype.sizeReturns theMapThe total number of members of an instance.

Methods for Map instances fall into two broad categories: operation methods (for manipulating data) and traversal methods (for traversing members).

Four operation methods:

  • Map.prototype.set(key,value): Sets the key namekeyThe corresponding key value isvalue, and return to the entire Map structure. ifkeyIf there is already a value, the key value is updated, otherwise the key is generated.
  • Map.prototype.get(key)Reading:keyCorresponding key value, if can’t findkeyTo return toundefined.
  • Map.prototype.has(key): Returns a Boolean value indicating whether a key is in the current Map object.
  • Map.prototype.delete(key): Deletes a keytrue. If the deletion fails, returnfalse.
  • Map.prototype.clear(): Clears all members with no return value.

Four traversal methods:

  • Map.prototype.keys(): Returns the key name traverser
  • Map.prototype.values(): returns the key value traverser
  • Map.prototype.entries(): returns the key value pair traverser
  • Map.prototype.forEach(): Iterates through each member using the callback function

Example 1: Extension object

When we have a list of objects, we want to record one property per object. Suppose you have 100 chickens and you need to record the weight of each chicken. There are two ideas:

  1. Find a way to write on the chicken
  2. Record it in a book
class Chicken {}/ / 100 chickens
let chickenList = []
for (let i = 0; i < 100; i++) {
  chickenList.push(new Chicken())
}
                   
// Method 1: Record on chicken
chickenList.forEach(function(chicken, index){
	chicken.weight = getWeight(chicken);
});

// Method 2: Record it in a notebook
let notebook = [];
chickenList.forEach(function(chicken, index){
	notebook[index] = getWeight(chicken);
});
Copy the code

The first approach has the following problems:

  1. Destroy the appearance of the chicken, sometimes this is very serious, for example, you want to sell a 5 kg chicken as 6 kg, the result of the chicken directly write “I only 5 kg” (modify the original object, may lead to unexpected behavior)
  2. You may encounter some battle chickens that can’t write a word (objects are frozen or have unoverwritable properties)
  3. It is possible to write something that is already written, so that it is not visible at all (conflicts with the object’s original properties).

The second method has the following problems:

  1. The book does not have an exact one-to-one correspondence with the chickens, but relies on indexes or markers (such as giving each chicken a name) to record the correspondence (which cannot be accurately compared to which object).

At this point, you can use maps to extend objects

// Record to another book
let notebook = new Map(a); chickenList.forEach(function(chicken, index){
	notebook.set(chicken, getWeight(chicken));
});
Copy the code

Example 2: Improving the implementation of private attributes

Looking back at the previous version of Symbol’s private property implementation, there are still defects that can be traversed by special apis.

Solution based on Map:

Each generated object is extended with a Map inside a closure

var Person = (function() {
  var map = new Map(a);function Person(name) {
    map.set(this,name);
  }

  Person.prototype.getName = function() {
    return map.get(this);
  };

  returnPerson; } ());Copy the code

WeakMap

Similar to WeakSet introduced earlier, there are two differences between WeakMap and Map.

  • WeakMapThe key can only be an object, not another type of value.
  • WeakMapReferences to keys in are weak references

Similarly, WeakMap cannot traverse because the members are weak references and could disappear at any time.

WeakMap has only four methods available: get(), set(), has(), and delete().

Note: WeakMap weakly references only the key name, not the key value. Key values are still normal references.

const wm = new WeakMap(a);let key = {};
let obj = {foo: 1};

wm.set(key, obj);
obj = null;
wm.get(key)
Copy the code

Example: Improving the implementation of private attributes

There is another problem with the previous Map-based implementation:

When the external references to the Person instance are removed, the Map in the closure will still have a Reference to the Person instance as the key. The Person instance will not be garbage collected until all external references to the Person instance are removed and the closure in which the Map is located is removed

To solve this problem, WeakMap is used to further improve:

var Person = (function() {
  var wm = new WeakMap(a);function Person(name) {
    wm.set(this,name);
  }

  Person.prototype.getName = function() {
    return wm.get(this);
  };

  returnPerson; } ());Copy the code

Proxy

Before ES6, Object.defineProperty could intercept the reading and modification of Object attributes, and Proxy could be understood as more powerful than this API, erecting a layer of “interception” in front of the target Object. All external access to the Proxy object must pass this layer of interception. Therefore, the Proxy provides a mechanism for filtering and rewriting external access. The word Proxy is used to mean that it acts as a Proxy for certain operations.

Note: Interception only works on generated Proxy instance operations

Generate Proxy instance:

var proxy = new Proxy(target, handler);
Copy the code
  • Target: the object to be proxied
  • Handler: Collection of intercepting functions

If handler is an empty object, it means that no operation is intercepted

let obj = {}

/* Handler is empty object */
let proxy = new Proxy(obj, {});
proxy.a = 1
//obj.a //1
Copy the code

Intercepting reads of attributes:

var proxy = new Proxy({}, {
  get: function(target, property) {
    return 35; }}); proxy.time/ / 35
proxy.name / / 35
proxy.title / / 35
Copy the code

The following is a list of 13 interception operations supported by Proxy.

  • get(target, propKey, receiver): intercepts the reading of object properties, such asproxy.fooandproxy['foo'].
  • set(target, propKey, value, receiver): Intercepts the setting of object properties, such asproxy.foo = vorproxy['foo'] = v, returns a Boolean value.
  • has(target, propKey)Intercept:propKey in proxyReturns a Boolean value.
  • deleteProperty(target, propKey)Intercept:delete proxy[propKey]Returns a Boolean value.
  • ownKeys(target)Intercept:Object.getOwnPropertyNames(proxy),Object.getOwnPropertySymbols(proxy),Object.keys(proxy),for... inLoop to return an array. This method returns the property names of all of the target object’s own properties, andObject.keys()The return result of the object contains only the traversable properties of the target object itself.
  • getOwnPropertyDescriptor(target, propKey)Intercept:Object.getOwnPropertyDescriptor(proxy, propKey)Returns the description object of the property.
  • defineProperty(target, propKey, propDesc)Intercept:Object. DefineProperty (proxy, propKey propDesc),Object.defineProperties(proxy, propDescs), returns a Boolean value.
  • getPrototypeOf(target)Intercept:Object.getPrototypeOf(proxy), returns an object.
  • setPrototypeOf(target, proto)Intercept:Object.setPrototypeOf(proxy, proto), returns a Boolean value. If the target object is a function, there are two additional operations that can be intercepted.
  • apply(target, object, args): Intercepts operations called by Proxy instances as functions, such asproxy(... args),proxy.call(object, ... args),proxy.apply(...).
  • construct(target, args): intercepts operations called by Proxy instances as constructors, such asnew proxy(... args).
  • isExtensible(target)Intercept:Object.isExtensible(proxy), returns a Boolean value.
  • preventExtensions(target)Intercept:Object.preventExtensions(proxy), returns a Boolean value.

Proxy gives developers permission to intercept the default behavior of the language and can be easily used in many scenarios without changing the original object or function. For example: counting function calls, implementing responsive data observation (Vue 3.0), implementing Immutable data, and so on

Reflect

Reflect is a new API that ES6 provides for manipulating objects. ES6 focuses much of the language-level API of the previous version, such as Object.defineProperty delete in, on static methods for Reflect.

(1) Put some methods of Object that are clearly internal to the language (such as Object.defineProperty) on Reflect. At this stage, some methods are deployed on both Object and Reflect objects, and future new methods will only be deployed on Reflect objects. That is, from the Reflect object you can get the methods inside the language.

(2) Modify the return result of some Object methods to make them more reasonable.

/ / the old way
try {
  Object.defineProperty(target, property, attributes);
  // success
} catch (e) {
  // failure
}

/ / a new way
if (Reflect.defineProperty(target, property, attributes)) {
  // success
} else {
  // failure
}
Copy the code

(3) Convert imperative operations into function calls to avoid more reserved word occupation. Reflect.has(obj, name) and reflect.deleteProperty (obj, name)

/ / the old way
'assign' in Object // true

/ / a new way
Reflect.has(Object.'assign') // true
Copy the code

(4) Reflect object method and Proxy object method one to one correspondence, want to call the default behavior, directly call the same method on Reflect, simple and reliable, eliminating the need to manually write the default behavior code.

let proxy = new Proxy({}, {
  set: function(target, name, value, receiver) {
    var success = Reflect.set(target, name, value, receiver);
    if (success) {
      console.log('property ' + name + ' on ' + target + ' set to ' + value);
    }
    returnsuccess; }});Copy the code

There are 13 static methods on the Reflect object.

  • Reflect.apply(target, thisArg, args)
  • Reflect.construct(target, args)
  • Reflect.get(target, name, receiver)
  • Reflect.set(target, name, value, receiver)
  • Reflect.defineProperty(target, name, desc)
  • Reflect.deleteProperty(target, name)
  • Reflect.has(target, name)
  • Reflect.ownKeys(target)
  • Reflect.isExtensible(target)
  • Reflect.preventExtensions(target)
  • Reflect.getOwnPropertyDescriptor(target, name)
  • Reflect.getPrototypeOf(target)
  • Reflect.setPrototypeOf(target, prototype)

The actions of the above methods correspond to those of the Proxy object handler.

Iterator

An Iterator is an object. The Iterator needs to contain a next method that returns an object with two attributes, one value indicating the current result and one done indicating whether the iteration can continue

let it = makeIterator();

function makeIterator() {
  let nextIndex = 0;
  return {
    next: function() {
      return nextIndex < 5 ?
      {value: nextIndex++, done: false}, {value: undefined.done: true}; }}; }Copy the code

ES6 states that a data structure is considered “iterable” if its symbol. iterator property is a method that returns an iterator object.

interface Iterable {
  [Symbol.iterator]() : Iterator,
}

interfaceIterator { next(value? :any) : IterationResult,
}

interface IterationResult {
  value: any.done: boolean,}Copy the code

Example:

let obj = {
  [Symbol.iterator]:makeIterator
}
Copy the code

The Iterator interface (symbol. Iterator method) is invoked by default in ES6 in the following situations:

  • for... ofcycle
  • An array of deconstruction
  • Extended operator
  • yield*
  • Where other implicit calls are used, for examplenew Set(['a','b']).Promise.all()Etc.

The following data structures in ES6 are traversable objects by default, that is, the symbol.iterator property is deployed by default

  • Array
  • Map
  • Set
  • String
  • The arguments object for the function
  • The NodeList object

The Generator function

The basic concept

Generator functions are an asynchronous programming solution provided by ES6 and behave completely differently from traditional functions.

Defining Generator functions

function* f() {}Copy the code

Formally, a Generator function is an ordinary function, but has two characteristics. There is an asterisk between the function keyword and the function name. Second, you can use the yield keyword inside the function body to define different internal states (yield means “output” in English).

Executing Generator functions

The Generator does not execute itself. Instead, it returns an iterator object that is also iterable because it has symbol. iterator methods on its prototype chain and returns the iterator object itself

function* f() {
  console.log(1)}let a = f()
a[Symbol.iterator]() === a // true
Copy the code

Therefore, an object returned by a Generator function can also be iterated, equivalent to calling the value of the object next() each time as the result of the iteration

The Generator will execute only if the next() method of the traverser object is executed:

function* f() {
   console.log(1)}let a = f()
a.next() / / print 1 returns {value: undefined, done: true}
Copy the code

Yield and yield

The yield keyword can be used in Generator functions to define the value after each next() of the iterator object returned by the function

function* f() {  
    yield 1
}
let a = f()
a.next()  {value: 1, done: false}
Copy the code

And each time A executes next(), it pauses at the next yield until there is no yield keyword following, then executes the rest of the code and returns done:true:

function* f() {
  console.log('step1')
  yield 1
  console.log('step2');   
  yield 2
  console.log('step3');
}
let a = f()
a.next() Step1 returns {value: 1, done: false}
a.next() // Print step2 return {value: 2, done: false}
a.next() Return {value: undefined, done: true}
Copy the code

Yield itself does not return a value; the yield return is the value passed in by the next() function.

So the next() method does two things

  1. To perform theyieldTo the nextyieldCode between
  2. Pass the value of the parameter to thisyieldThe return value of the

Next () and yield transfer control inside and outside the function.

function* f() {  
    console.log('start');  
    let result = yield 1  
    console.log('result:',result);
}
let a = f()
Copy the code

Yield * is equivalent to iterating over an object and yield each result

function* f() {  
    yield 'start'  
    yield* [1.2.3]  
    /* is the same as */  
    // for(let value of [1,2,3]){  
    // yield value
    // }  
    yield 'end'
}
let a = f()
a.next() {value: 'start', done: false}
a.next() {value: 1, done: false}
a.next() {value: 2, done: false}
a.next() {value: 3, done: false}
a.next() {value: 'end', done: false}
a.next() {value: undefined, done: true}
Copy the code

Generator functions with automatic actuators

Problems with direct loops

The Generator function is a new asynchronous programming solution, but calling next() manually every time is cumbersome. What if we wrote a loop to execute next()?

function* f() {
  yield 1
  console.log('1');
  yield 2
  console.log(Completed '2');
}
let it = f()
let done = false
while(! done){ done = it.next().done }Copy the code

This may seem fine, but if the yield itself is an asynchronous operation, it can be

function* f() {
  yield readFile(file1)
  console.log('Yeah, that's one.');
  yield readFile(file2)
  console.log('Yeah, that's two.');
}
let it = f()
let done = false
while(! done){ done = it.next().done }// Complete 1
// Yes, done 2
Copy the code

If the requirement is to execute the code after yield after the asynchronous operation completes, then this order of execution would not be appropriate. Validation:

function* f() {
  yield readFile(file1,function (err,data) {
    console.log('Read data 1:' + data)
  })
  console.log('Yeah, that's one.');
  yield readFile(file2,function (err,data) {
    console.log('Read data 2:' + data)
  })
  console.log('Yeah, that's two.');
}

let it = f()
let done = false
while(! done){ done = it.next().done }// Complete 1
// Yes, done 2
// Read data 1:111
// Read data 2:222
Copy the code

Thunk function

In the JavaScript language, a Thunk function is a function that takes multiple arguments and replaces it with a single-argument function that takes only a callback as an argument.

// Thunk readFile (single-argument version)
const {readFile} = require('fs')
const path = require('path')
const file1 = path.join(__dirname,'./text/1.txt')
const file2 = path.join(__dirname,'./text/2.txt')
let Thunk = function (fileName) {
  return function (callback) {
    return readFile(fileName, callback);
  };
};

let readFileThunk = Thunk(file1);
readFileThunk(function(err,data){
  console.log(String(data));
});
Copy the code

There is a Thunkify library that makes it easy to turn apis into Thunk functions

autoactuator

Write an auto-executor run function that wraps the logic of it.next() into nextStep() each time and passes nextStep as a callback to the thunk-formatted file-reading function.

// Thunk readFile (single-argument version)
const {readFile} = require('fs')
const path = require('path')
const file1 = path.join(__dirname, './text/1.txt')
const file2 = path.join(__dirname, './text/2.txt')
let Thunk = function (fileName) {
  return function (callback) { //result.value
    return readFile(fileName, callback);
  };
};

function* f() {
  let data1 = yield Thunk(file1)
  console.log('Yeah, we're done 1, the data is' + data1);
  let data2 = yield Thunk(file2)
  console.log('Yeah, we're done with 2, the data is' + data2);
}

function run(f) {
  let it = f();

  function nextStep(err, data) {
    var result = it.next(data);
    if (result.done) return;
    result.value(nextStep);  // Execute readFile and pass nextStep as a callback
  }

  nextStep();
}

run(f)
Copy the code

As a result, writing asynchronous logic is as simple in form as writing synchronous logic, based on the autoexecutor, as long as the asynchronous operation is a Thunk function or returns a Promise.

Co module

The CO module is a better autoexecutor for a wrapper, supporting yield types, including not only thunk functions, but also Promise objects, arrays, objects, and even Generator functions

const { promisify } = require("util");
const path = require('path')
const file1 = path.join(__dirname, './text/1.txt')
const file2 = path.join(__dirname, './text/2.txt')
const readFileP = promisify(readFile)
let Thunk = function (fileName) {
  return function (callback) { //result.value
    return readFile(fileName, callback);
  };
};

/*Thunk*/
function* f() {
  let data1 = yield Thunk(file1)
  console.log('Yeah, we're done 1, the data is' + data1);
  let data2 = yield Thunk(file2)
  console.log('Yeah, we're done with 2, the data is' + data2);
}

/*Promise*/
function* f() {
  let data1 = yield readFileP(file1)
  console.log('Yeah, we're done 1, the data is' + data1);
  let data2 = yield readFileP(file2)
  console.log('Yeah, we're done with 2, the data is' + data2);
}
/* Array (concurrent) */
function* f() {
  let data = yield [readFileP(file1),readFileP(file2)]
  console.log('Yeah, we're done. The data is.' + data);
}

/* Object (concurrent) */
function* f() {
  let data = yield {data1:readFileP(file1),data2:readFileP(file2)}
  console.log('Yeah, we're done. The data is.' + JSON.stringify(data));
}

/ * * / Generator function
function* f() {
  function* f1() {
    return yield {data1:readFileP(file1),data2:readFileP(file2)}
  }
  let data = yield f1()
  console.log('Yeah, we're done. The data is.' + JSON.stringify(data));
}
co(f)
Copy the code

Generator after execution by a CO module returns a Promise object:

co(f).then(() = >{
  console.log('CO completed');
})
Copy the code

Async function

The basic concept

What is async function? In short, it is the syntactic sugar of Generator functions.

Change the code from the previous chapter to the async function version:

const { promisify } = require("util");
const path = require('path')
const file1 = path.join(__dirname, './text/1.txt')
const file2 = path.join(__dirname, './text/2.txt')
const readFileP = promisify(readFile)

function* f() {
  let data1 = yield readFileP(file1)
  console.log('Yeah, we're done 1, the data is' + data1);
  let data2 = yield readFileP(file2)
  console.log('Yeah, we're done with 2, the data is' + data2);
}

// Version of async function
async function f() {
  let data1 = await readFileP(file1)
  console.log('Yeah, we're done 1, the data is' + data1);
  let data2 = await readFileP(file2)
  console.log('Yeah, we're done with 2, the data is' + data2);
}

Copy the code

The comparison shows that the version of async replaces the asterisk (*) of Generator functions with async and yields with await.

Define async functions

Define an async function using the async keyword:

async function f() {
  let data1 = await readFileP(file1)
  console.log('Yeah, we're done 1, the data is' + data1);
  let data2 = await readFileP(file2)
  console.log('Yeah, we're done with 2, the data is' + data2);
}
Copy the code

Execute async functions

An async function executes a Generator function that runs automatically. If an async function returns a result other than a Promise, it returns the result wrapped as a Promise:

async function f() {
  console.log(1);
}
f().then(() = >{
  console.log(2);
})

async function f() {
  console.log(1);
  return 'done'
}

f().then(value= > {
  console.log(value);
})
Copy the code

The await keyword

Similar to yield, async functions can use the await keyword. After the await keyword, a Promise instance will be written. Each time an async function executes an await keyword, control will be transferred back to the external environment.

  1. ifawaitIf the Promise instance is resolved, the Promise instance will be resolvedawaitThe next timeawaitBetween the code pushed toMircoTask (microtask)Is waiting for execution, andawaitIs the value of the Promise instance resolve
  2. ifawaitIf it is not followed by a Promise instance, this will be immediately changedawaitThe next timeawaitBetween the code pushed toMircoTask (microtask)Is waiting for execution, andawaitIs equal toawaitThe value of the following expression:
async function f() {
  let data = await new Promise((resolve, reject) = > {
    setTimeout(() = > {
      resolve('a')},2000)})console.log(data);
}

//f()
//console.log('end')
Copy the code

If await is not followed by a Promise instance

async function f() {
  let data = await 'a'
  console.log(data);
}
f()
console.log('end'); 
//end
//a
Copy the code

Error handling of async functions

If a Promise is rejected or thrown, the code after await will not execute, so try.. To catch an error with await:

async function f() {
  try {
    let data = await new Promise((resolve, reject) = > {
      setTimeout(() = > {
        reject('123')},2000)})// Subsequent code cannot be executed
    console.log('done');
  }catch (e) {
    console.log('Error:',e);
  }
}
f()
Copy the code

Async functions handle concurrent asynchronous tasks

If each await in an async function is executed after the preceding await resolve, use promise.all if you want to execute concurrently:

/* Concurrent processing asynchronous */
async function f() {
  let time1 = new Date(a)let [data1,data2] = await Promise.all([
    new Promise((resolve, reject) = > {
      setTimeout(() = > {
        resolve('123')},2000)}),new Promise((resolve, reject) = > {
      setTimeout(() = > {
        resolve('123')},3000)})])console.log(data1,data2,'Time:'+ (new Date() - time1));
}
f()
Copy the code

Async function vs. Promise

Using async functions to write asynchronous logic is more concise than Promise, dealing with different asynchronous results interdependence, error handling, if… Else branches are much easier:

const {readFile} = require('fs')
const { promisify } = require("util");
const path = require('path')
const file1 = path.join(__dirname, './text/1.txt')
const file2 = path.join(__dirname, './text/2.txt')
const file3 = path.join(__dirname, './text/3.txt')
const readFileP = promisify(readFile)

function f1() {
  readFileP(file1).then(data1= >{
    console.log('Yeah, we're done 1, the data is' + data1);
    return readFileP(file2)
  }).then(data2= > {
    console.log('Yeah, we're done 1, the data is' + data2);
    return readFileP(file3)
  }).then(data3= > {
    console.log('Yeah, we're done 1, the data is'+ data3); })}async function f2() {
  let data1 = await readFileP(file1)
  console.log('Yeah, we're done 1, the data is' + data1);
  let data2 = await readFileP(file2)
  console.log('Yeah, we're done with 2, the data is' + data1 + data2);
  let data3 = await readFileP(file3)
  console.log('Yeah, we're done with 2, the data is' + data1 + data2 + data3);

}
f()
Copy the code