1 async函数
1.1 介绍
ES8引入async
函数,是为了使异步操作更加方便,其实它就是Generator函数的语法糖。
async
函数使用起来,只要把Generator函数的(*)号换成async
,yield
换成await
即可。对比如下:
// Generator写法
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());
};
// async await写法
const asyncReadFile = async function () {
const f1 = await readFile('/etc/fstab');
const f2 = await readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
对比Genenrator有四个优点:
- (1)内置执行器
Generator函数执行需要有执行器,而async
函数自带执行器,即async
函数与普通函数一模一样:
async f();
- (2)更好的语义
async
和await
,比起星号
和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
就会先返回,等到异步操作完成,在继续执行。
async function f(item){
let a = await g(item);
let b = await h(item);
return b;
}
f('hello').then(res => {
console.log(res);
})
async
表明该函数内部有异步操作,调用该函数时,会立即返回一个Promise对象。
另外还有个定时的案例,指定时间后执行:
function f (ms){
return new Promise(res => {
setTimeout(res, ms);
});
}
async function g(val, ms){
await f(ms);
console.log(val);
}
g('leo', 50);
async
函数还有很多使用形式:
// 函数声明
async function f (){...}
// 函数表达式
let f = async function (){...}
// 对象的方法
let a = {
async f(){...}
}
a.f().then(...)
// Class的方法
class c {
constructor(){...}
async f(){...}
}
// 箭头函数
let f = async () => {...}
1.3 返回Promise对象
async
内部return
返回的值会作为then
方法的参数,另外只有async
函数内部的异步操作执行完,才会执行then
方法指定的回调函数。
async function f(){
return 'leo';
}
f().then(res => { console.log (res) }); // 'leo'
async
内部抛出的错误,会被catch
接收。
async function f(){
throw new Error('err');
}
f().then (
v => console.log(v),
e => console.log(e)
)
// Error: err
1.4 await命令
通常await
后面是一个Promise对象,如果不是就返回对应的值。
async function f(){
return await 10;
}
f().then(v => console.log(v)); // 10
我们常常将async await
和try..catch
一起使用,并且可以放多个await
命令,也是防止异步操作失败因为中断后续异步操作的情况。
async function f(){
try{
await Promise.reject('err');
}catch(err){ ... }
return await Promise.resolve('leo');
}
f().then(v => console.log(v)); // 'leo'
1.5 使用注意
- (1)
await
命令放在try...catch
代码块中,防止Promise返回rejected
。 - (2)若多个
await
后面的异步操作不存在继发关系,最好让他们同时执行。
// 效率低
let a = await f();
let b = await g();
// 效率高
let [a, b] = await Promise.all([f(), g()]);
// 或者
let a = f();
let b = g();
let a1 = await a();
let b1 = await b();
- (3)
await
命令只能用在async
函数之中,如果用在普通函数,就会报错。
// 错误
async function f(){
let a = [{}, {}, {}];
a.forEach(v =>{ // 报错,forEach是普通函数
await post(v);
});
}
// 正确
async function f(){
let a = [{}, {}, {}];
for(let k of a){
await post(k);
}
}
2 Promise.prototype.finally()
finally()
是ES8中Promise添加的一个新标准,用于指定不管Promise对象最后状态(是fulfilled
还是rejected
)如何,都会执行此操作,并且finally
方法必须写在最后面,即在then
和catch
方法后面。
// 写法如下:
promise
.then(res => {...})
.catch(err => {...})
.finally(() => {...})
finally
方法常用在处理Promise请求后关闭服务器连接:
server.listen(port)
.then(() => {..})
.finally(server.stop);
本质上,finally方法是then方法的特例:
promise.finally(() => {...});
// 等同于
promise.then(
result => {
// ...
return result
},
error => {
// ...
throw error
}
)
3 Object.values(),Object.entries()
ES7中新增加的 Object.values()
和Object.entries()
与之前的Object.keys()
类似,返回数组类型。
回顾下Object.keys()
:
var a = { f1: 'hi', f2: 'leo'};
Object.keys(a); // ['f1', 'f2']
3.1 Object.values()
返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历属性的键值。
let a = { f1: 'hi', f2: 'leo'};
Object.values(a); // ['hi', 'leo']
如果参数不是对象,则返回空数组:
Object.values(10); // []
Object.values(true); // []
3.2 Object.entries()
返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历属性的键值对数组。
let a = { f1: 'hi', f2: 'leo'};
Object.entries(a); // [['f1','hi'], ['f2', 'leo']]
- 用途1:
遍历对象属性。
let a = { f1: 'hi', f2: 'leo'};
for (let [k, v] of Object.entries(a)){
console.log(
`${JSON.stringfy(k)}:${JSON.stringfy(v)}`
)
}
// 'f1':'hi'
// 'f2':'leo'
- 用途2:
将对象转为真正的Map结构。
let a = { f1: 'hi', f2: 'leo'};
let map = new Map(Object.entries(a));
手动实现Object.entries()
方法:
// Generator函数实现:
function* entries(obj){
for (let k of Object.keys(obj)){
yield [k ,obj[k]];
}
}
// 非Generator函数实现:
function entries (obj){
let arr = [];
for(let k of Object.keys(obj)){
arr.push([k, obj[k]]);
}
return arr;
}
4 Object.getOwnPropertyDescriptors()
之前有Object.getOwnPropertyDescriptor
方法会返回某个对象属性的描述对象,新增的Object.getOwnPropertyDescriptors()
方法,返回指定对象所有自身属性(非继承属性)的描述对象,所有原对象的属性名都是该对象的属性名,对应的属性值就是该属性的描述对象
let a = {
a1:1,
get f1(){ return 100}
}
Object.getOwnPropetyDescriptors(a);
/*
{
a:{ configurable:true, enumerable:true, value:1, writeable:true}
f1:{ configurable:true, enumerable:true, get:f, set:undefined}
}
*/
实现原理:
function getOwnPropertyDescriptors(obj) {
const result = {};
for (let key of Reflect.ownKeys(obj)) {
result[key] = Object.getOwnPropertyDescriptor(obj, key);
}
return result;
}
引入这个方法,主要是为了解决Object.assign()
无法正确拷贝get
属性和set
属性的问题。
let a = {
set f(v){
console.log(v)
}
}
let b = {};
Object.assign(b, a);
Object.a(b, 'f');
/*
f = {
configurable: true,
enumable: true,
value: undefined,
writeable: true
}
*/
value
为undefined
是因为Object.assign
方法不会拷贝其中的get
和set
方法,使用getOwnPropertyDescriptors
配合Object.defineProperties
方法来实现正确的拷贝:
let a = {
set f(v){
console.log(v)
}
}
let b = {};
Object.defineProperties(b, Object.getOwnPropertyDescriptors(a));
Object.getOwnPropertyDescriptor(b, 'f')
/*
configurable: true,
enumable: true,
get: undefined,
set: function(){...}
*/
Object.getOwnPropertyDescriptors
方法的配合Object.create
方法,将对象属性克隆到一个新对象,实现浅拷贝。
const clone = Object.create(Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj));
// 或者
const shallowClone = (obj) => Object.create(
Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj)
);
5 字符串填充 padStart和padEnd
用来为字符串填充特定字符串,并且都有两个参数:字符串目标长度和填充字段,第二个参数可选,默认空格。
'es8'.padStart(2); // 'es8'
'es8'.padStart(5); // ' es8'
'es8'.padStart(6, 'woof'); // 'wooes8'
'es8'.padStart(14, 'wow'); // 'wowwowwowwoes8'
'es8'.padStart(7, '0'); // '0000es8'
'es8'.padEnd(2); // 'es8'
'es8'.padEnd(5); // 'es8 '
'es8'.padEnd(6, 'woof'); // 'es8woo'
'es8'.padEnd(14, 'wow'); // 'es8wowwowwowwo'
'es8'.padEnd(7, '6'); // 'es86666'
从上面结果来看,填充函数只有在字符长度小于目标长度时才有效,若字符长度已经等于或小于目标长度时,填充字符不会起作用,而且目标长度如果小于字符串本身长度时,字符串也不会做截断处理,只会原样输出。
6 函数参数列表与调用中的尾部逗号
该特性允许我们在定义或者调用函数时添加尾部逗号而不报错:
function es8(var1, var2, var3,) {
// ...
}
es8(10, 20, 30,);
7 共享内存与原子操作
当内存被共享时,多个线程可以并发读、写内存中相同的数据。原子操作可以确保那些被读、写的值都是可预期的,即新的事务是在旧的事务结束之后启动的,旧的事务在结束之前并不会被中断。这部分主要介绍了 ES8 中新的构造函数 SharedArrayBuffer
以及拥有许多静态方法的命名空间对象 Atomic
。
Atomic
对象类似于 Math
对象,拥有许多静态方法,所以我们不能把它当做构造函数。 Atomic
对象有如下常用的静态方法:
- add /sub :为某个指定的value值在某个特定的位置增加或者减去某个值
- and / or /xor :进行位操作
- load :获取特定位置的值