10)Reflect: ES6 为了操作对象而提供的新 API

 

Reflect对象设计的目的:

(1)Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上。现阶段,某些方法同时在Object和Reflect对象上部署,未来的新方法将只部署在Reflect对象上。也就是说,从Reflect对象上可以拿到语言内部的方法

(2) 修改某些Object方法返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回false

(3)Object操作都变成函数行为。某些Object操作是命令式,比如name in obj和delete obj[name],而Reflect.has(obj, name)和Reflect.deleteProperty(obj, name)让它们变成了函数行为

(4)Reflect对象的方法Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。这就让Proxy对象可以方便地调用对应的Reflect方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy怎么修改默认行为,你总可以在Reflect上获取默认行为

 

Reflect对象一共有13个静态方法---与Proxy对象的方法一一对应

(3)Reflect.get(target, name, receiver)---查找并返回target对象的name属性,如果没有,返回undefined

//例1
var myObject = {
  foo: 1,
  bar: 2,
  get baz() {
    return this.foo + this.bar;
  },
}
Reflect.get(myObject, 'foo') // 1
Reflect.get(myObject, 'bar') // 2
Reflect.get(myObject, 'baz') // 3

//例2
//如果name属性部署了读取函数(getter),则读取函数的this绑定receiver
var myObject = {
  foo: 1,
  bar: 2,
  get baz() {
    return this.foo + this.bar;
  },
};

var myReceiverObject = {
  foo: 4,
  bar: 4,
};

Reflect.get(myObject, 'baz', myReceiverObject) // 8

//例3如果第一个参数不是对象,
Reflect.get
方法会报错
Reflect.get(1, 'foo') // 报错
Reflect.get(false, 'foo') // 报错

(4)Reflect.set(target, name, value, receiver)----设置target对象的name属性等于value

//例1
var myObject = {
  foo: 1,
  set bar(value) {
    return this.foo = value;
  },
}
myObject.foo // 1
Reflect.set(myObject, 'foo', 2);
myObject.foo // 2

Reflect.set(myObject, 'bar', 3)
myObject.foo // 3

//例2
//如果name属性设置了赋值函数,则赋值函数的this绑定receiver
var myObject = {
  foo: 4,
  set bar(value) {
    return this.foo = value;
  },
};

var myReceiverObject = {
  foo: 0,
};

Reflect.set(myObject, 'bar', 1, myReceiverObject);
myObject.foo // 4
myReceiverObject.foo // 1

 //例3
//如果第一个参数不是对象,Reflect.set会报错
Reflect.set(1, 'foo', {}) // 报错
Reflect.set(false, 'foo', {}) // 报错

(7)Reflect.has(target, name)----对应name in obj里面的in运算符,返回boolean类型值

//如果第一个参数不是对象,Reflect.has和in运算符都会报错。
var myObject = {
  foo: 1,
};

// 旧写法
'foo' in myObject // true

// 新写法
Reflect.has(myObject, 'foo') // true

(5)Reflect.defineProperty(target, name, desc)----等同于Object.defineProperty,用来为对象定义属性

//例1
function MyDate() {
  /*…*/
}

// 旧写法
Object.defineProperty(MyDate, 'now', {
  value: () => Date.now()
});

// 新写法
Reflect.defineProperty(MyDate, 'now', {
  value: () => Date.now()
});

//例2
//第一个参数不是对象,就会抛出错误
Reflect.defineProperty(1, 'foo')   //抛出错误

(6)Reflect.deleteProperty(target, name)---等同于delete obj[name],用于删除对象的属性,返回boolean

//删除成功或者删除对象不存在,返回true
const myObj = { foo: 'bar' };

// 旧写法
delete myObj.foo;

// 新写法
Reflect.deleteProperty(myObj, 'foo');

(11)Reflect.getOwnPropertyDescriptor(target, name)---等同于Object.getOwnPropertyDescriptor,用于得到指定属性的描述对象

//第一个参数不是对象,Object.getOwnPropertyDescriptor(1, 'foo')不报错,返回undefined,
//而Reflect.getOwnPropertyDescriptor(1, 'foo')会抛出错误,表示参数非法
var myObject = {};
Object.defineProperty(myObject, 'hidden', {
  value: true,
  enumerable: false,
});

// 旧写法
var theDescriptor = Object.getOwnPropertyDescriptor(myObject, 'hidden');

// 新写法
var theDescriptor = Reflect.getOwnPropertyDescriptor(myObject, 'hidden');

(12)Reflect.getPrototypeOf(target)---读取对象的__proto__属性,对应Object.getPrototypeOf(obj)

//例1
const myObj = new FancyThing();

// 旧写法
Object.getPrototypeOf(myObj) === FancyThing.prototype;

// 新写法
Reflect.getPrototypeOf(myObj) === FancyThing.prototype;

//例2
//如果参数不是对象,Object.getPrototypeOf会将这个参数转为对象,然后再运行,而Reflect.getPrototypeOf会报错
Object.getPrototypeOf(1) // Number {[[PrimitiveValue]]: 0}
Reflect.getPrototypeOf(1) // 报错

(13)Reflect.setPrototypeOf(target, prototype)----设置目标对象的原型,返回boolean表示设置是否成功

//例1
const myObj = {};
// 旧写法
Object.setPrototypeOf(myObj, Array.prototype);
// 新写法
Reflect.setPrototypeOf(myObj, Array.prototype);
myObj.length // 0

//例2
//无法设置目标对象的原型
Reflect.setPrototypeOf({}, null)
// true
Reflect.setPrototypeOf(Object.freeze({}), null)
// false

//例3
//如果第一个参数不是对象,Object.setPrototypeOf会返回第一个参数本身,而Reflect.setPrototypeOf会报错
Object.setPrototypeOf(1, {})
// 1

Reflect.setPrototypeOf(1, {})
// TypeError: Reflect.setPrototypeOf called on non-object

//例4
//第一个参数是undefined或null,Object.setPrototypeOf和Reflect.setPrototypeOf都会报错
Object.setPrototypeOf(null, {})
// TypeError: Object.setPrototypeOf called on null or undefined

Reflect.setPrototypeOf(null, {})
// TypeError: Reflect.setPrototypeOf called on non-object

(8)Reflect.ownKeys(target)----返回对象的所有属性,基本等同于Object.getOwnPropertyNames与Object.getOwnPropertySymbols之和

var myObject = {
  foo: 1,
  bar: 2,
  [Symbol.for('baz')]: 3,
  [Symbol.for('bing')]: 4,
};

// 旧写法
Object.getOwnPropertyNames(myObject)
// ['foo', 'bar']

Object.getOwnPropertySymbols(myObject)
//[Symbol(baz), Symbol(bing)]

// 新写法
Reflect.ownKeys(myObject)
// ['foo', 'bar', Symbol(baz), Symbol(bing)]

 

(9)Reflect.isExtensible(target)---对应Object.isExtensible,返回一个布尔值,表示当前对象是否可扩展

 

//例1
const myObject = {};
// 旧写法
Object.isExtensible(myObject) // true
// 新写法
Reflect.isExtensible(myObject) // true

//例2
Object.isExtensible(1) // false
Reflect.isExtensible(1) // 报错

 

(10)Reflect.preventExtensions(target)----对应Object.preventExtensions方法,用于让一个对象变为不可扩展,返回布尔值,表示是否操作成功

 

//例1
var myObject = {};
// 旧写法
Object.preventExtensions(myObject) // Object {}
// 新写法
Reflect.preventExtensions(myObject) // true

//例2,参数不是对象,object.preventExtensions
在 ES5 环境报错,在 ES6 环境返回传入的参数,而Reflect.preventExtensions
会报错
// ES5 环境
Object.preventExtensions(1) // 报错

// ES6 环境
Object.preventExtensions(1) // 1

// 新写法
Reflect.preventExtensions(1) // 报错

(1)Reflect.apply(target, thisArg, args)----同于Function.prototype.apply.call(func, thisArg, args),用于绑定this对象后执行给定函数

如果要绑定一个函数的this对象,可以这样写fn.apply(obj, args),但是如果函数定义了自己的apply方法,就只能写成Function.prototype.apply.call(fn, obj, args),采用Reflect对象可以简化这种操作

const ages = [11, 33, 12, 54, 18, 96];

// 旧写法
const youngest = Math.min.apply(Math, ages);
const oldest = Math.max.apply(Math, ages);
const type = Object.prototype.toString.call(youngest);

// 新写法
const youngest = Reflect.apply(Math.min, Math, ages);
const oldest = Reflect.apply(Math.max, Math, ages);
const type = Reflect.apply(Object.prototype.toString, youngest, []);

(2)Reflect.construct(target, args)----等同于new target(...args),这提供了一种不使用new来调用构造函数的方法

function Greeting(name) {
  this.name = name;
}

// new 的写法
const instance = new Greeting('张三');

// Reflect.construct 的写法
const instance = Reflect.construct(Greeting, ['张三']);

11)Iterator( for-of循环 ): ES6

 

具有Iterator接口的数据结构:  Array     String       set      map     NodeList 还可以组合它们

 

Iterator的定义(遍历器--迭代器)

1)它是一种接口,为各种不同的数据结构提供统一的访问机制

2)任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。

 

Iterator的作用

1)各种数据结构,提供一个统一的、简便的访问接口

2)数据结构的成员能够按某种次序排列

3)ES6 创造了一种新的遍历命令for...of循环Iterator 接口主要供for...of消费

 

原生具备Iterator接口的数据结构

Array   String   Set   Map   arguments   NodeList

Iterator 的遍历过程

(1)创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象

(2)第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员

(3)第二次调用指针对象的next方法,指针就指向数据结构的第二个成员

(4)不断调用指针对象的next方法,直到它指向数据结构的结束位置

每一次调用next方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含valuedone两个属性的对象。其中,value属性是当前成员的值done属性是一个布尔值,true表示遍历结束

var it = makeIterator(['a', 'b']);

it.next() // { value: "a", done: false }
it.next() // { value: "b", done: false }
it.next() // { value: undefined, done: true }

function makeIterator(array) {
  var nextIndex = 0;
  return {
    next: function() {
      return nextIndex < array.length ?
        {value: array[nextIndex++], done: false} :
        {value: undefined, done: true};
    }
  };
}

 

12)Generator( 生成器 ): ES6 提供的一种异步编程解决方案
(1)语法上Generator 函数是一个状态机,封装了多个内部状态

(2)执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态

Generator 函数在形式上有2个特征

(1)function关键字与函数名之间有一个星号
(2)函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)
function* helloWorldGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}

var hw = helloWorldGenerator();

调用 Generator 函数后,返回的是一个指向内部状态的指针对象,也就是遍历器
接着调用遍历器对象的next方法,使指针移向下一个状态。每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。yield表达式是暂停执行的标记,而next方法可以恢复执行

 

hw.next()
// { value: 'hello', done: false }

hw.next()
// { value: 'world', done: false }

hw.next()
// { value: 'ending', done: true }

hw.next()
// { value: undefined, done: true }

yield表达式只能用在Generator函数里面

//yield表达式如果用在另一个表达式之中,必须放在圆括号里面
function* demo() {
  console.log('Hello' + yield); // SyntaxError
  console.log('Hello' + yield 123); // SyntaxError

  console.log('Hello' + (yield)); // OK
  console.log('Hello' + (yield 123)); // OK
}

//yield表达式用作函数参数或放在赋值表达式的右边,可以不加括号
function* demo() {
  foo(yield 'a', yield 'b'); // OK
  let input = yield; // OK
}

ES6 没有规定,function关键字与函数名之间的星号,写在哪个位置。这导致下面的写法都能通过

function * foo(x, y) { ··· }
function *foo(x, y) { ··· }
function* foo(x, y) { ··· } //常用形式
function*foo(x, y) { ··· }

Generator 函数可以不用yield表达式,这时就变成了一个单纯的暂缓执行函数

function* f() {
  console.log('执行了!')
}

var generator = f();      //如果是普通函数,这里会马上执行

setTimeout(function () {  
  generator.next() //只有调用next()才会执行
}, 2000);

next()方法的参数

 

function* f() {
  for(var i = 0; true; i++) {
    var reset = yield i;
    if(reset) { i = -1; }
  }
}

var g = f();

g.next() // { value: 0, done: false },第一次调用next()不需要传参
g.next() // { value: 1, done: false }
g.next(true) // { value: 0, done: false }

 

function* foo(x) {
  var y = 2 * (yield (x + 1));
  var z = yield (y / 3);
  return (x + y + z);
}

// 将generator赋给变量a
// 第一个next()用来启动遍历器对象,所以不用带有参数
// 从第二次使用next方法开始,参数才是有效的,
// next方法的参数表示上一个yield表达式的返回值
var a = foo(5);
console.log(a.next()) // Object{value:6, done:false}
console.log(a.next()) // Object{value:NaN, done:false}
console.log(a.next()) // Object{value:NaN, done:true}

var b = foo(5);
console.log(b.next()) // { value:6, done:false }
console.log(b.next(12)) // { value:8, done:false },12作为yield (x + 1)中的额(x+1)的值
console.log(b.next(13)) // { value:42, done:true },13作为yield (y / 3)中(y / 3)的值

 for...of循环可以自动遍历 Generator 函数时生成的Iterator对象,且此时不再需要调用next方法

//一旦next方法的返回对象的done属性为true,for...of循环就会中止,
//且不包含该返回对象,所以上面代码的return语句返回的6,不包括在for...of循环之中
function* foo() {
  yield 1;
  yield 2;
  yield 3;
  yield 4;
  yield 5;
  return 6;
}

for (let v of foo()) {
  console.log(v);
}
// 1 2 3 4 5

 

Generator.prototype原型上的两个方法

1)Generator.prototype.throw()---Generator 函数返回的遍历器对象,都有一个throw方法,可以在函数体外抛出错误,然后在 Generator 函数体内通过try....catch捕获,throw方法抛出的错误要被内部捕获,前提是必须至少执行过一次next方法

var g = function* () {
  try {
    yield;
  } catch (e) {
    console.log('内部捕获', e);
  }
};

var i = g();
i.next();

try {
  i.throw('a');
  i.throw('b');
} catch (e) {
  console.log('外部捕获', e);
}
// 内部捕获 a
// 外部捕获 b

如果 Generator 函数内部没有部署try...catch代码块,那么throw方法抛出的错误,将被外部try...catch代码块捕获

ar g = function* () {
  while (true) {
    yield;
    console.log('内部捕获', e);
  }
};

var i = g();
i.next();

try {
  i.throw('a');
  i.throw('b');
} catch (e) {
  console.log('外部捕获', e);
}
// 外部捕获 a

如果 Generator 函数内部和外部,都没有部署try...catch代码块,那么程序将报错,直接中断执行

var gen = function* gen(){
  yield console.log('hello');
  yield console.log('world');
}

var g = gen();
g.next();
g.throw();
// hello
// Uncaught undefined

throw方法被捕获以后,会附带执行下一条yield表达式。也就是说,会附带执行一次next方法

var gen = function* gen(){
  try {
    yield console.log('a');
  } catch (e) {
    // ...
  }
  yield console.log('b');
  yield console.log('c');
}

var g = gen();
g.next() // a
g.throw() // b
g.next() // c

throw命令与g.throw方法是无关的,两者互不影响

var gen = function* gen(){
  yield console.log('hello');
  yield console.log('world');
}

var g = gen();
g.next();

try {
  throw new Error();
} catch (e) {
  g.next();
}
// hello
// world

Generator 函数体外抛出的错误,可以在函数体内捕获;反过来,Generator 函数体内抛出的错误,也可以被函数体外的catch捕获

unction* foo() {
  var x = yield 3;
  var y = x.toUpperCase();
  yield y;
}

var it = foo();

it.next(); // { value:3, done:false }

try {
  it.next(42);
} catch (err) {
  console.log(err);
}

一旦 Generator 执行过程中抛出错误,且没有被内部捕获,就不会再执行下去了。如果此后还调用next方法,将返回一个value属性等于undefineddone属性

function* g() {
  yield 1;
  console.log('throwing an exception');
  throw new Error('generator broke!');
  yield 2;
  yield 3;
}

function log(generator) {
  var v;
  console.log('starting generator');
  try {
    v = generator.next();
    console.log('第一次运行next方法', v);
  } catch (err) {
    console.log('捕捉错误', v);
  }
  try {
    v = generator.next();
    console.log('第二次运行next方法', v);
  } catch (err) {
    console.log('捕捉错误', v);
  }
  try {
    v = generator.next();
    console.log('第三次运行next方法', v);
  } catch (err) {
    console.log('捕捉错误', v);
  }
  console.log('caller done');
}

log(g());
// starting generator
// 第一次运行next方法 { value: 1, done: false }
// throwing an exception
// 捕捉错误 { value: 1, done: false }
// 第三次运行next方法 { value: undefined, done: true }
// caller done

2)Generator.prototype.return()-----可以返回给定的值,并且终结遍历 Generator 函数

function* gen() {
  yield 1;
  yield 2;
  yield 3;
}

var g = gen();

g.next()        // { value: 1, done: false }
g.return('foo') // { value: "foo", done: true },如果return方法调用时,不提供参数,则返回值的value属性为undefined
g.next()        // { value: undefined, done: true }

如果 Generator 函数内部有try...finally代码块,那么return方法会推迟到finally代码块执行完再执行

function* numbers () {
  yield 1;
  try {
    yield 2;
    yield 3;
  } finally {
    yield 4;
    yield 5;
  }
  yield 6;
}
var g = numbers();
g.next() // { value: 1, done: false }
g.next() // { value: 2, done: false }
g.return(7) // { value: 4, done: false }
g.next() // { value: 5, done: false }
g.next() // { value: 7, done: true }

next()   throw()  return()

共同点:都是让 Generator 函数恢复执行,并且使用不同的语句替换yield表达式

//1、next()是将yield表达式替换成一个值
const g = function* (x, y) {
  let result = yield x + y;
  return result;
};

const gen = g(1, 2);
gen.next(); // Object {value: 3, done: false}

gen.next(1); // Object {value: 1, done: true}
// 相当于将 let result = yield x + y
// 替换成 let result = 1;


//2、throw()是将yield表达式替换成一个throw语句
gen.throw(new Error('出错了')); // Uncaught Error: 出错了
// 相当于将 let result = yield x + y
// 替换成 let result = throw(new Error('出错了'));

//3、return()是将yield表达式替换成一个return语句
gen.return(2); // Object {value: 2, done: true}
// 相当于将 let result = yield x + y
// 替换成 let result = return 2;

yield*-----在 Generator 函数内部,调用另一个 Generator 函数

//1、默认在一个Generator里面调用另外一个Generator不生效
function* foo() {
  yield 'a';
  yield 'b';
}

function* bar() {
  yield 'x';
  foo();
  yield 'y';
}

for (let v of bar()){
  console.log(v);
}
// "x"
// "y"

//2、使用yield*
function* bar() {
  yield 'x';
  yield* foo();
  yield 'y';
}

// 等同于
function* bar() {
  yield 'x';
  yield 'a';
  yield 'b';
  yield 'y';
}

// 等同于
function* bar() {
  yield 'x';
  for (let v of foo()) {
    yield v;
  }
  yield 'y';
}

for (let v of bar()){
  console.log(v);
}
// "x"
// "a"
// "b"
// "y"

//3、function* inner() {
  yield 'hello!';
}

function* outer1() {
  yield 'open';
  yield inner();
  yield 'close';
}

var gen = outer1()
gen.next().value // "open"
gen.next().value // 返回一个遍历器对象
gen.next().value // "close"

function* outer2() {
  yield 'open'
  yield* inner()
  yield 'close'
}

var gen = outer2()
gen.next().value // "open"
gen.next().value // "hello!"
gen.next().value // "close"

 

// 例1、使用generator处理异步调用
// 用setTimeout来设置异步执行回调函数
// let getDataOne = (cb)=>{
//   setTimeout( ()=>{ cb('dataOne') }, 1000 );
// };
// let getDataTwo = (cb)=>{
//   setTimeout( ()=>{ cb('dataTwo') }, 1000 );
// };
// // 调用
// getDataOne( (data)=>{console.log(data)} );
// getDataTwo( (data)=>{console.log(data)} );


// 例2、下面用generator实现
// 修改getDataOne、getDataTwo的函数
let generator;
let getDataOne = ()=>{
  setTimeout( ()=>{ generator.next('dataOne') }, 1000 );//调用generator.next(val)进行传参,并调用函数
};
let getDataTwo = ()=>{
  setTimeout( ()=>{ generator.next('dataTwo') }, 1000 );
}
// 定义generator函数
function* main(){
  let dataone = yield getDataOne();//使用yield来暂停执行程序
  let datatwo = yield getDataTwo();
  console.log(dataone);
  console.log(datatwo);
}
// 定义generator实例,并触发next()函数
generator = main();
generator.next(); //输出dataOne   dataTwo

Generator的应用

1)异步操作的同步化表达

用Generator实现ajax

function* main() {
  var result = yield request("http://some.url");
  var resp = JSON.parse(result);
    console.log(resp.value);
}

function request(url) {
  makeAjaxCall(url, function(response){
    it.next(response);
  });
}

var it = main();
it.next();

2)控制流管理,如果一个函数的返回值是下一个函数的输入值如此循环

//同步操作
function* longRunningTask(value1) {
  try {
    var value2 = yield step1(value1);
    var value3 = yield step2(value2);
    var value4 = yield step3(value3);
    var value5 = yield step4(value4);
    // Do something with value4
  } catch (e) {
    // Handle any error from step1 through step4
  }
}

//使用一个函数,按次序自动执行所有步骤
scheduler(longRunningTask(initialValue));

function scheduler(task) {
  var taskObj = task.next(task.value);
  // 如果Generator函数未结束,就继续调用
  if (!taskObj.done) {
    task.value = taskObj.value
    scheduler(task);
  }
}

3)部署Iteretor接口

function* iterEntries(obj) {
  let keys = Object.keys(obj);
  for (let i=0; i < keys.length; i++) {
    let key = keys[i];
    yield [key, obj[key]];
  }
}

let myObj = { foo: 3, bar: 7 };

for (let [key, value] of iterEntries(myObj)) {
  console.log(key, value);
}

// foo 3
// bar 7

 

13)async: ES6 提供的一种异步编程解决方案

定义:async是 Generator 函数的语法糖,async函数就是将 Generator 函数的星号*)替换成async,将yield替换成await

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());
};



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函数对 Generator 函数的改进

a)内置执行器----Generator 函数的执行必须靠执行器(显示调用next()),才有了co模块;async函数的执行,与普通函数一模一样,只要一行

asyncReadFile();

b)更好的语义----async(vs *)表示函数里有异步操作await( yield )表示紧跟在后面的表达式需要等待结果

c)更广的适用性-----co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象;await命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)----被转成一个立即resolve的 Promise 对象

async function f() {
  return await 123;
}

f().then(v => console.log(v))
// 123

await命令后面的 Promise 对象如果变为reject状态,则reject的参数会被catch方法的回调函数接收到

async function f() {
  await Promise.reject('出错了');
}

f()
.then(v => console.log(v))
.catch(e => console.log(e))
// 出错了

 

d)返回值是 Promise-----Generator 函数的返回值是 Iterator 对象方便多,可以调用then()指定下一步的操作

 

基本用法

async函数返回一个 Promise 对象,可以使then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。

// 指定 50 毫秒以后,输出hello world
function timeout(ms) {
  return new Promise((resolve) => {
    console.log(1);
    setTimeout(resolve, ms);
  });
}

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

asyncPrint('hello world', 50);

async函数声明的 方式-----5种

// 1、函数声明
async function foo() {}

///2、函数表达式
const foo = async function () {};

///3、对象的方法
let obj = { async foo() {} };
obj.foo().then(...)

///4、Class 的方法
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(…);

// 5、箭头函数
const foo = async () => {};

语法

返回值:

async函数返回一个 Promise 对象,内部return语句返回的值,会成为then方法回调函数的参数

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

f().then(v => console.log(v))
// "hello world"

错误处理--

async函数内部抛出错误,会导致返回的 Promise 对象变为reject状态。抛出的错误对象会被catch方法回调函数接收到

async function f() {
  throw new Error('出错了');
}

f().then(
  v => console.log(v),
  e => console.log(e)
)
// Error: 出错了

前一个异步操作失败,也不要中断后面的异步操作

//1、第一个await放在try...catch结构里面,这样不管这个异步操作是否成功,第二个await都会执行
async function f() {
  try {
    await Promise.reject('出错了');
  } catch(e) {
  }
  return await Promise.resolve('hello world');
}

f()
.then(v => console.log(v))
// hello world

//2、await后面的 Promise 对象再跟一个catch方法,处理前面可能出现的错误
async function f() {
  await Promise.reject('出错了')
    .catch(e => console.log(e));
  return await Promise.resolve('hello world');
}

f()
.then(v => console.log(v))
// 出错了
// hello world

 

Promise对象状态变化

只有async函数内部所有的异步操作执行完,才会执行then方法指定的回调函数,除非遇到return语句或者抛出错误

//函数getTitle内部有三个操作:抓取网页、取出文本、匹配页面标题。只有这三个操作全部完成,
//才会执行then方法里面的console.log
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"

 

使用注意点

a)await命令后面的Promise对象,运行结果可能是rejected,所以最好把await命令放在try...catch代码块中

async function myFunction() {
  try {
    await somethingThatReturnsAPromise();
  } catch (err) {
    console.log(err);
  }
}

// 另一种写法

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

b)多个await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发

//继发关系,互不依赖,耗时
let foo = await getFoo();
let bar = await getBar();


//非继发关系
// 写法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);

// 写法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;

c)await命令只能用在async函数之中,如果用在普通函数,就会报错

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

  // 报错
  docs.forEach(function (doc) {
    await db.post(doc);
  });
}

function dbFuc(db) { //这里不需要 async
  let docs = [{}, {}, {}];

  // 可能得到错误结果,三个db.post操作将是并发执行
  docs.forEach(async function (doc) {
    await db.post(doc);
  });
}

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

  for (let doc of docs) {
    await db.post(doc);
  }
}

 

async函数实现原理:将 Generator 函数和自动执行器,包装在一个函数里

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

// 等同于

function fn(args) {
  return spawn(function* () {  //spawn函数就是自动执行器
    // ...
  });
}


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); });
  });
}

js中实现异步编程的方法

回调、事件监听 、发布-订阅、Promise对象、Generator、Async

回调----通过函数的嵌套

Promise----将回调函数的嵌套,改成链式调用,通过then()方法实现

Generator----可以暂停执行和恢复执行,这是它能封装异步任务的根本原因.函数体内外的数据交换next()和错误处理机制throw()

 

异步方法比较---Promise   Genarator   async

假定某个 DOM 元素上面,部署了一系列的动画,前一个动画结束,才能开始后一个。如果当中有一个动画出错,就不再往下执行,返回上一个成功执行的动画的返回值

//1、Promise 的写法比回调函数的写法大大改进,但是一眼看上去,代码完全都是 Promise 
//的 API(then、catch等等),操作本身的语义反而不容易看出来
function chainAnimationsPromise(elem, animations) {

  // 变量ret用来保存上一个动画的返回值
  let ret = null;

  // 新建一个空的Promise
  let p = Promise.resolve();

  // 使用then方法,添加所有动画
  for(let anim of animations) {
    p = p.then(function(val) {
      ret = val;
      return anim(elem);
    });
  }

  // 返回一个部署了错误捕捉机制的Promise
  return p.catch(function(e) {
    /* 忽略错误,继续执行 */
  }).then(function() {
    return ret;
  });

}



//2、Generator
//用 Generator 函数遍历了每个动画,语义比 Promise 写法更清晰,用户定义的操作全部都出
//现在spawn函数的内部
//问题,必须有一个自动执行器,spawn函数就是自动执行器,它返回一个 Promise 对象,而且必须保证//yield语句后面的表达式,必须返回一个 Promise。
function chainAnimationsGenerator(elem, animations) {

  return spawn(function*() {
    let ret = null;
    try {
      for(let anim of animations) {
        ret = yield anim(elem);
      }
    } catch(e) {
      /* 忽略错误,继续执行 */
    }
    return ret;
  });

}

//3、async函数
//实现最简洁,最符合语义
async function chainAnimationsAsync(elem, animations) {
  let ret = null;
  try {
    for(let anim of animations) {
      ret = await anim(elem);
    }
  } catch(e) {
    /* 忽略错误,继续执行 */
  }
  return ret;
}

 

 

 

41、ES7----ECMAScript2016

ES7比ES6就多了3种新特性:新的数学运算符     新的数组方法    新的语法错误

1)新的数学运算符 -----指数运算符 (唯一一个js语法变化)------求幂运算符 **    基数**指数

已经有Math.pow(基数,指数)

运算顺序----大于+ - * / %     运算限制-----左侧的一元表达式只能使用++   --

// 1
let num = 3;
console.log( num--**2 )//9    num=2

// 2
let num = 3;
console.log( ++num**2 )//16    num=4

// 3
console.log( -5**2 )//出错

// 4
console.log( 2*5**2 )//50

2)新的数组方法 -----Array.prototype.includes( elem [,index] )----elem要搜索的值,index开始检索的地方(可省略,默认为0)

返回值:存在----true     不存在----false

值的比较---NaN在indexOf()中不相等,在includes()中相等;+0和-0在indexOf()和includes()相等,在Object.is()不相等

 

3)函数作用域严格模式的一处改动---只有参数为不包含解构、默认值的简单参数列表时,才可以在函数中使用use strict

 

 

 
Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐