async/await 就是 Generator 的语法糖,使得异步操作变得更加方便。其中Generator的*换成了async关键字, yield换成了await关键字。
Async函数与Generator相比,甜在哪里?
- async函数内置执行器,函数调用之后会自动执行,输出最后的结果。而Generator需要配合next和co模块一起使用。
- 更好的语义, async表示函数里有异步操作,而await表示紧跟在后面的表达式需要等待结果。
- 更广的适用性,co模块约定, yield后面只能是thunk函数或者Promise对象,但是async可以是Promise对象和原始类型的值 。
- async的返回值是Promise对象,而Generator返回值是iterator。
Async的实现原理,就是将Generator和自动执行函数包装在函数里。
下面是看的博客的分析实现,自己理解分析一下
function my_co(it) {
return new Promise( (resolve, reject) => {
function next(data) {
try{
let {value, done} = it.next(data);
}catch(err) {
reject(err);
}
if(!done) {
Promise.resolve(value).then( val => next(val), reject);
}else{
resolve(value);
}
}
next();
});
}
实际运行一下
function* test() {
yield new Promise( (resolve, reject) => {
setTimeout(resolve(10), 100);
});
yield new Promise( (resolve, reject) => {
resolve(100);
});
yield 10;
return 1000;
}
my_co(test()).then(val => console.log(val) ).catch(err => console.log(err)) //output 1000
下面是执行过程分析
function my_co(it) {
return new Promise( (resolve, reject) => {
function next(data) { //第一次调用,data的值为undefined,第二次调用data为promsie[10],第三次掉用为romise[100],第四次调用为10
try {
var {value, done} = it.next(data); // {value:promise obj, done:false}, data=undefined;{value:promise obj, done:false}, data=10; {value:10, done:false}, data=100;{value:undefined, done:true}, data=10
} catch (error) {
reject(error);
}
if(!done) { //true
//有可能值时vlaue或者promise对象
Promise.resolve(value).then((val) => { //第一次value:promise, 第二次value:promise,第三次为10
console.log(value)
next(val);
}, reject);
}else{
console.log(value);
resolve(value); //第四次调用 value为1000,决议完成(上面的test中若是没有返回值或者返回值为空,则value为undefined)
}
}
next(); //执行一次
});
}
更新补充
Promise.race实现
> Pormsie.race特点分析 + Promise.race返回的仍然是一个Promise. 它的状态与第一个完成的Promise的状态相同。它可以是完成( resolves),也可以是失败(rejects),这要取决于第一个Promise是哪一种状态。 + 如果传入的参数是不可迭代的,那么将会抛出错误。 + 如果传的参数数组是空,那么返回的 promise 将永远等待。 + 如果迭代包含一个或多个非承诺值和/或已解决/拒绝的承诺,则 Promise.race 将解析为迭代中找到的第一个值 Promise.race = function (promises) {
return new Promise( (resolve, reject) => {
if(promises[Symbol.iterator] != 'function') {
console.log('args must be iteratable');
}
if(promises.length == 0) {
return;
}else{
for(let promise of promises) {
Promise.resolve(promise).then( (val) => {resolve(val); return;},
err => {reject(err); return;} );
}
}
})
}
//tests
Promise.race([]).then( (data) => console.log('success', data) , err=> console.log('error', err));
Promise.race().then( (data) => console.log('success', data) , err=> console.log('error', err));
Promise.race([
new Promise((resolve, reject) => setTimeout(resolve(100), 100)),
new Promise((resolve, reject) => setTimeout(resolve(200), 100)),
new Promise((resolve, reject) => setTimeout(resolve(300), 100))
]).then( (data) => console.log('success', data) , err=> console.log('error', err));
可遍历数据结构有什么特点
一个对象如果要具备可被 for...of 循环调用的 Iterator 接口,就必须在其 Symbol.iterator 的属性上部署遍历器生成方法(或者原型链上的对象具有该方法)
let obj = {
name: 'eng',
age: 18,
job: "engineer"
[Symbol.iterator]() {
const self = this;
const key = Object.keys(self); //Object.keys method返回一个自身属性名的数组,顺序是按照正常的遍历顺序
let index = 0;
return {
next() {
if(index < keys.length) {
return {
value: self[key[index++]],
done: false;
}
}else {
return {
value: undefined,
done: true
}
}
}
}
}
}
for(let i of obj) {
console.log(i);
}
//上面的方法可以用Generator进行重写
let obj = {
name: "eng",
age: 18,
job: "engineer",
*[Symbol.iterator]() {
let self = this;
let key = Object.keys(self);
for(let index = 0; index < key.length; index++) {
yield self[key[index]];
}
}
}
for(let i of obj) {
console.log(i);
}
/**
* browerlist: {
* "IE": ">=9"
* }
*
* 为了兼容,我们需要pollyfill
* */
if(!Object.keys) {
Object.keys = (function() {
'ues strict';
var hasOwnProperty = Object.prototype.hasOwnProperty,
hasDontEnumBug = !({ toString: null}).propertyIsEnumerable('toString'),
dontEnums = [
'toString',
'toLocaleString',
'valueOf',
'hasOwnProperty',
'isPrototypeOf',
'propertyIsEnumerable',
'constructor'
],
dontEnumLength = dontEnums.length;
return function(obj) {
if (typeof obj !== 'function' && (typeof obj !== 'object' || obj === null)) {
throw new TypeError('Object.keys called on non-object');
}
var result = [], prop, i;
for (prop in obj) {
if (hasOwnProperty.call(obj, prop)) {
result.push(prop);
}
}
if (hasDontEnumBug) {
for (i = 0; i < dontEnumsLength; i++) {
if (hasOwnProperty.call(obj, dontEnums[i])) {
result.push(dontEnums[i]);
}
}
}
return result;
};
})()
}
原生具备Iterator接口的对象如下
- Array
- Map
- Set
- String
- TypedArray
- 函数的Arguments对象
- NodeList对象
- ES6的数组 、Set、Map都有entries()、values()、keys()都返回可迭代对象。
实现双向绑定 Proxy 与 Object.defineProperty 相比优劣如何?
- Object.definedProperty 的作用是劫持一个对象的属性,劫持属性的getter和setter方法,在对象的属性发生变化时进行特定的操作。而 Proxy 劫持的是整个对象。
- Proxy 会返回一个代理对象,我们只需要操作新对象即可,而 Object.defineProperty 只能遍历对象属性直接修改
- Object.definedProperty 不支持数组,更准确的说是不支持数组的各种API,因为如果仅仅考虑arry[i] = value 这种情况,是可以劫持的,但是这种劫持意义不大。而 Proxy 可以支持数组的各种API。
- 尽管Object.defineProperty有很多缺陷,但是兼容性是比Proxy好的
ps: Vue2.x 使用 Object.defineProperty 实现数据双向绑定,V3.0 则使用了 Proxy.
//拦截器
let obj = {};
let temp = 'ai';
Object.defineProperty(obj, 'name', {
get() {
console.log('get success');
return temp;
},
set(value) {
console.log('set success');
temp = value;
}
})
obj.name = "chris";
console.log(obj.name);
ps: Object.defineProperty 定义出来的属性,默认是不可枚举,不可更改,不可配置【无法delete】
我们可以看到 Proxy 会劫持整个对象,读取对象中的属性或者是修改属性值,那么就会被劫持。但是有点需要注意,复杂数据类型,监控的是引用地址,而不是值,如果引用地址没有改变,那么不会触发set。
let obj = {name:'ai', habbits: ['travel', 'swimming'], info: {
age:10,
job: 'engineer'
}}
let p = new Proxy(obj, {
get(target, key){
console.log('get success');
return Reflect.get(targte, key)
},
set(target, key, value) {
console.log('set success');
Reflect.set(target, key, value);
}
});
p.name = 20; //设置成功
p.age = 20; //设置成功; 不需要事先定义此属性
p.hobbits.push('photography'); //读取成功;注意不会触发设置成功
p.info.age = 18; //读取成功;不会触发设置成功
我们再看下对于数组的劫持,Object.definedProperty 和 Proxy 的差别。Object.definedProperty 可以将数组的索引作为属性进行劫持,但是仅支持直接对arry[i]进行操作,不支持数组的API,非常鸡肋。
let array = [];
Object.defineProperty(array, '0', {
get() {
console.log('get success');
return temp;
},
set(value) {
console.log('set success');
temp = value;
}
});
array[0] = 10;
array.push(11);
Proxy 可以监听到数组的变化,支持各种API。注意数组的变化触发get和set可能不止一次,如有需要,自行根据key值决定是否要进行处理
let array = ['travel', 'swim'];
let p = new Proxy(array, {
get(target, key){
console.log('get success');
return Reflect.get(target, key);
},
set(target, key, value) {
console.log('set success');
return Reflect.set(target, key, value);
}
});
p.push('running'); //触发get 和 set
p.pop();