1 async函数

1.1 介绍

ES8引入async函数,是为了使异步操作更加方便,其实它就是Generator函数的语法糖。
async函数使用起来,只要把Generator函数的(*)号换成asyncyield换成await即可。对比如下:

  1. // Generator写法
  2. const fs = require('fs');
  3. const readFile = function (fileName) {
  4. return new Promise(function (resolve, reject) {
  5. fs.readFile(fileName, function(error, data) {
  6. if (error) return reject(error);
  7. resolve(data);
  8. });
  9. });
  10. };
  11. const gen = function* () {
  12. const f1 = yield readFile('/etc/fstab');
  13. const f2 = yield readFile('/etc/shells');
  14. console.log(f1.toString());
  15. console.log(f2.toString());
  16. };
  17. // async await写法
  18. const asyncReadFile = async function () {
  19. const f1 = await readFile('/etc/fstab');
  20. const f2 = await readFile('/etc/shells');
  21. console.log(f1.toString());
  22. console.log(f2.toString());
  23. };

对比Genenrator有四个优点:

  • (1)内置执行器
    Generator函数执行需要有执行器,而async函数自带执行器,即async函数与普通函数一模一样:
  1. async f();
  • (2)更好的语义
    asyncawait,比起星号yield,语义更清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。
  • (3)更广的适用性
    yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)。
  • (4)返回值是Promise
    async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用then方法指定下一步的操作。

进一步说,async函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await命令就是内部then命令的语法糖。

1.2 基本用法

async函数返回一个Promise对象,可以使用then方法添加回调函数,函数执行时,遇到await就会先返回,等到异步操作完成,在继续执行。

  1. async function f(item){
  2. let a = await g(item);
  3. let b = await h(item);
  4. return b;
  5. }
  6. f('hello').then(res => {
  7. console.log(res);
  8. })

async表明该函数内部有异步操作,调用该函数时,会立即返回一个Promise对象。
另外还有个定时的案例,指定时间后执行:

  1. function f (ms){
  2. return new Promise(res => {
  3. setTimeout(res, ms);
  4. });
  5. }
  6. async function g(val, ms){
  7. await f(ms);
  8. console.log(val);
  9. }
  10. g('leo', 50);

async函数还有很多使用形式:

  1. // 函数声明
  2. async function f (){...}
  3. // 函数表达式
  4. let f = async function (){...}
  5. // 对象的方法
  6. let a = {
  7. async f(){...}
  8. }
  9. a.f().then(...)
  10. // Class的方法
  11. class c {
  12. constructor(){...}
  13. async f(){...}
  14. }
  15. // 箭头函数
  16. let f = async () => {...}

1.3 返回Promise对象

async内部return返回的值会作为then方法的参数,另外只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。

  1. async function f(){
  2. return 'leo';
  3. }
  4. f().then(res => { console.log (res) }); // 'leo'

async内部抛出的错误,会被catch接收。

  1. async function f(){
  2. throw new Error('err');
  3. }
  4. f().then (
  5. v => console.log(v),
  6. e => console.log(e)
  7. )
  8. // Error: err

1.4 await命令

通常await后面是一个Promise对象,如果不是就返回对应的值。

  1. async function f(){
  2. return await 10;
  3. }
  4. f().then(v => console.log(v)); // 10

我们常常将async awaittry..catch一起使用,并且可以放多个await命令,也是防止异步操作失败因为中断后续异步操作的情况。

  1. async function f(){
  2. try{
  3. await Promise.reject('err');
  4. }catch(err){ ... }
  5. return await Promise.resolve('leo');
  6. }
  7. f().then(v => console.log(v)); // 'leo'

1.5 使用注意

  • (1)await命令放在try...catch代码块中,防止Promise返回rejected
  • (2)若多个await后面的异步操作不存在继发关系,最好让他们同时执行。
  1. // 效率低
  2. let a = await f();
  3. let b = await g();
  4. // 效率高
  5. let [a, b] = await Promise.all([f(), g()]);
  6. // 或者
  7. let a = f();
  8. let b = g();
  9. let a1 = await a();
  10. let b1 = await b();
  • (3)await命令只能用在async函数之中,如果用在普通函数,就会报错。
  1. // 错误
  2. async function f(){
  3. let a = [{}, {}, {}];
  4. a.forEach(v =>{ // 报错,forEach是普通函数
  5. await post(v);
  6. });
  7. }
  8. // 正确
  9. async function f(){
  10. let a = [{}, {}, {}];
  11. for(let k of a){
  12. await post(k);
  13. }
  14. }

⬆ 返回目录

2 Promise.prototype.finally()

finally()是ES8中Promise添加的一个新标准,用于指定不管Promise对象最后状态(是fulfilled还是rejected)如何,都会执行此操作,并且finally方法必须写在最后面,即在thencatch方法后面。

  1. // 写法如下:
  2. promise
  3. .then(res => {...})
  4. .catch(err => {...})
  5. .finally(() => {...})

finally方法常用在处理Promise请求后关闭服务器连接:

  1. server.listen(port)
  2. .then(() => {..})
  3. .finally(server.stop);

本质上,finally方法是then方法的特例:

  1. promise.finally(() => {...});
  2. // 等同于
  3. promise.then(
  4. result => {
  5. // ...
  6. return result
  7. },
  8. error => {
  9. // ...
  10. throw error
  11. }
  12. )

⬆ 返回目录

3 Object.values(),Object.entries()

ES7中新增加的 Object.values()Object.entries()与之前的Object.keys()类似,返回数组类型。
回顾下Object.keys()

  1. var a = { f1: 'hi', f2: 'leo'};
  2. Object.keys(a); // ['f1', 'f2']

3.1 Object.values()

返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历属性的键值。

  1. let a = { f1: 'hi', f2: 'leo'};
  2. Object.values(a); // ['hi', 'leo']

如果参数不是对象,则返回空数组:

  1. Object.values(10); // []
  2. Object.values(true); // []

3.2 Object.entries()

返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历属性的键值对数组。

  1. let a = { f1: 'hi', f2: 'leo'};
  2. Object.entries(a); // [['f1','hi'], ['f2', 'leo']]
  • 用途1
    遍历对象属性。
  1. let a = { f1: 'hi', f2: 'leo'};
  2. for (let [k, v] of Object.entries(a)){
  3. console.log(
  4. `${JSON.stringfy(k)}:${JSON.stringfy(v)}`
  5. )
  6. }
  7. // 'f1':'hi'
  8. // 'f2':'leo'
  • 用途2
    将对象转为真正的Map结构。
  1. let a = { f1: 'hi', f2: 'leo'};
  2. let map = new Map(Object.entries(a));

手动实现Object.entries()方法:

  1. // Generator函数实现:
  2. function* entries(obj){
  3. for (let k of Object.keys(obj)){
  4. yield [k ,obj[k]];
  5. }
  6. }
  7. // 非Generator函数实现:
  8. function entries (obj){
  9. let arr = [];
  10. for(let k of Object.keys(obj)){
  11. arr.push([k, obj[k]]);
  12. }
  13. return arr;
  14. }

⬆ 返回目录

4 Object.getOwnPropertyDescriptors()

之前有Object.getOwnPropertyDescriptor方法会返回某个对象属性的描述对象,新增的Object.getOwnPropertyDescriptors()方法,返回指定对象所有自身属性(非继承属性)的描述对象,所有原对象的属性名都是该对象的属性名,对应的属性值就是该属性的描述对象

  1. let a = {
  2. a1:1,
  3. get f1(){ return 100}
  4. }
  5. Object.getOwnPropetyDescriptors(a);
  6. /*
  7. {
  8. a:{ configurable:true, enumerable:true, value:1, writeable:true}
  9. f1:{ configurable:true, enumerable:true, get:f, set:undefined}
  10. }
  11. */

实现原理:

  1. function getOwnPropertyDescriptors(obj) {
  2. const result = {};
  3. for (let key of Reflect.ownKeys(obj)) {
  4. result[key] = Object.getOwnPropertyDescriptor(obj, key);
  5. }
  6. return result;
  7. }

引入这个方法,主要是为了解决Object.assign()无法正确拷贝get属性和set属性的问题。

  1. let a = {
  2. set f(v){
  3. console.log(v)
  4. }
  5. }
  6. let b = {};
  7. Object.assign(b, a);
  8. Object.a(b, 'f');
  9. /*
  10. f = {
  11. configurable: true,
  12. enumable: true,
  13. value: undefined,
  14. writeable: true
  15. }
  16. */

valueundefined是因为Object.assign方法不会拷贝其中的getset方法,使用getOwnPropertyDescriptors配合Object.defineProperties方法来实现正确的拷贝:

  1. let a = {
  2. set f(v){
  3. console.log(v)
  4. }
  5. }
  6. let b = {};
  7. Object.defineProperties(b, Object.getOwnPropertyDescriptors(a));
  8. Object.getOwnPropertyDescriptor(b, 'f')
  9. /*
  10. configurable: true,
  11. enumable: true,
  12. get: undefined,
  13. set: function(){...}
  14. */

Object.getOwnPropertyDescriptors方法的配合Object.create方法,将对象属性克隆到一个新对象,实现浅拷贝。

  1. const clone = Object.create(Object.getPrototypeOf(obj),
  2. Object.getOwnPropertyDescriptors(obj));
  3. // 或者
  4. const shallowClone = (obj) => Object.create(
  5. Object.getPrototypeOf(obj),
  6. Object.getOwnPropertyDescriptors(obj)
  7. );

⬆ 返回目录

5 字符串填充 padStart和padEnd

用来为字符串填充特定字符串,并且都有两个参数:字符串目标长度填充字段,第二个参数可选,默认空格。

  1. 'es8'.padStart(2); // 'es8'
  2. 'es8'.padStart(5); // ' es8'
  3. 'es8'.padStart(6, 'woof'); // 'wooes8'
  4. 'es8'.padStart(14, 'wow'); // 'wowwowwowwoes8'
  5. 'es8'.padStart(7, '0'); // '0000es8'
  6. 'es8'.padEnd(2); // 'es8'
  7. 'es8'.padEnd(5); // 'es8 '
  8. 'es8'.padEnd(6, 'woof'); // 'es8woo'
  9. 'es8'.padEnd(14, 'wow'); // 'es8wowwowwowwo'
  10. 'es8'.padEnd(7, '6'); // 'es86666'

从上面结果来看,填充函数只有在字符长度小于目标长度时才有效,若字符长度已经等于或小于目标长度时,填充字符不会起作用,而且目标长度如果小于字符串本身长度时,字符串也不会做截断处理,只会原样输出。

6 函数参数列表与调用中的尾部逗号

该特性允许我们在定义或者调用函数时添加尾部逗号而不报错:

  1. function es8(var1, var2, var3,) {
  2. // ...
  3. }
  4. es8(10, 20, 30,);

7 共享内存与原子操作

当内存被共享时,多个线程可以并发读、写内存中相同的数据。原子操作可以确保那些被读、写的值都是可预期的,即新的事务是在旧的事务结束之后启动的,旧的事务在结束之前并不会被中断。这部分主要介绍了 ES8 中新的构造函数 SharedArrayBuffer 以及拥有许多静态方法的命名空间对象 Atomic
Atomic 对象类似于 Math 对象,拥有许多静态方法,所以我们不能把它当做构造函数。 Atomic 对象有如下常用的静态方法:

  • add /sub :为某个指定的value值在某个特定的位置增加或者减去某个值
  • and / or /xor :进行位操作
  • load :获取特定位置的值

⬆ 返回目录