es6--js异步编程Generator、Promise、Async

万字长文,请根据右侧目录查阅所需!

Generator

打破运行,生成器,控制运行迭代器

基本概念

  • generator本身并不是用于处理异步的,但是能够实现!!!
    Generator函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。

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

跟普通函数的区别

  1. function关键字与函数名之间有一个星号–>"*";
  2. 函数体内部使用yield表达式,定义不同的内部状态。
  3. Generator函数不能跟new一起使用,会报错。
function* helloWorldGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}

var hw = helloWorldGenerator();

上面代码定义了一个 Generator 函数helloWorldGenerator,它内部有两个yield表达式(helloworld),即该函数有三个状态:hello,world 和 return 语句(结束执行)。

调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是遍历器对象(iterator)。

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

//如果需要调用,就必须使用yield创建的对象进行调用
function* helloWorldGenerator() {
  yield console.log("1");
  yield console.log("2");
  return 'ending';
}

var hw = helloWorldGenerator();
hw.next(); //1
hw.next(); //2
hw.next(); //{value: "ending", done: true}

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

function * foo(x, y) { ··· }
function *foo(x, y) { ··· }
function* foo(x, y) { ··· }
function*foo(x, y) { ··· }

yield 表达式与迭代器实例

生成器通过直接调用生成一个迭代器let it = foo();it.next(),迭代器使用next()可以控制yield运行!

由于 Generator 函数返回的遍历器对象,只有调用next方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。yield表达式就是暂停标志。

遍历器对象的next方法的运行逻辑如下。

(1)遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。

(2)下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式。

(3)如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。

(4)如果该函数没有return语句,则返回的对象的value属性值为undefined

yield表达式与return语句既有相似之处

都能返回紧跟在语句后面的那个表达式的值。

不同之处

每次遇到yield,函数暂停执行,下一次再从该位置继续向后执行,而return语句不具备位置记忆的功能。一个函数里面,只能执行一次(或者说一个)return语句,但是可以执行多次(或者说多个)yield表达式。正常函数只能返回一个值,因为只能执行一次return;Generator 函数可以返回一系列的值,因为可以有任意多个yield

注意:

yield表达式只能用在 Generator 函数里面,用在其他地方都会报错。

另外,yield表达式如果用在另一个表达式之中,必须放在圆括号里面。

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

与 Iterator 接口的关系

由于 Generator 函数就是遍历器生成函数本身就具备iterator特性,因此可以把 Generator 赋值给对象的Symbol.iterator属性,从而使得本没有遍历器接口Iterator的该对象具有 Iterator 接口。

Object.prototype[Symbol.iterator] = function* (){
  for(let i in this){
    yield this[i];
  }
}
//--------------Genertaor函数本身会返回具备Iterator接口的对象
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);
}

注意:

  • for…of本身会自动调用具备Iterator接口对象的next()方法,因为Array、Map、Set、arguments、String、NodeList都是继承与对象下面的。

  • 原生Object不具备Iterator结构,需要从其他地方借,比如上面说Object.prototype[Symbol.iterator]: Array.prototype[Symbol.iterator]

代码解读:

  • 给Object赋上Generator接口,那么就会自动得到Iterator遍历器的next方法,这个next方法在for…of里会自动调用并执行当前暂停的yield:
Object.prototype[Symbol.iterator] = function* () {
    for (let i in this) {
        yield this[i];
    }
}
let myObj = { foo: 3, bar: 7 };
console.log(myObj);

在这里插入图片描述

function* iterEntries(obj) {
    let keys = Object.keys(obj);//返回["foo","bar"]
    for (let i = 0; i < keys.length; i++) {
        let key = keys[i];
        yield [key, obj[key]];//通过yeild返回属性和属性值
    }
}

for (let [key, value] of iterEntries(myObj)) {//解构yield返回的属性和值
   console.log(key, value);//使用完后for..of自动触发下一次的next()
}

next 方法的参数

yield本身没有值,next方法可以带一个参数,该参数就会被当作上一个yield的值,这个值后yield后面的表达式无关。

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 }
g.next() // { value: 1, done: false }
g.next(true) // { value: 0, done: false }

这个功能有很重要的语法意义。

Generator 函数从暂停状态到恢复运行,它的上下文状态(context)是不变的。通过next方法的参数,就有办法在 Generator 函数开始运行之后,继续向函数体内部注入值。

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

var a = foo(5);
a.next() // Object{value:6, done:false}
a.next() // Object{value:NaN, done:false}
//这里没有传参,则上一个y的yield没有值,那么2*undefined就是NaN,那么y就是NaN,yield y/3就是NaN啦
a.next() // Object{value:NaN, done:true},NaN参与的计算都是NaN

var b = foo(5);
b.next() // { value:6, done:false }
b.next(12) // { value:8, done:false }
b.next(13) // { value:42, done:true }

协同操作的迭代器:生成器实例

  • 每次构建一个迭代器,实际上都会隐式地创建一个迭代器,而且引擎并没有阻止一个生成器创建多个迭代器,更没有阻止迭代器之间相互协作!
function* foo() {
    var x = yield 2; //y1
    z++;
    var y = yield (x*z);//y2
    console.log(x,y,z);
}

var z = 1;
var it1 = foo();
var it2 = foo();

var val1 = it1.next().value;//执行y1,val1=2
var val2 = it2.next().value;//执行y1,val2=2

val1 = it1.next(val2*10).value;//执行y2,给y1的yeild赋值20(val1=2,*10=20),it1的x值:x=y1的yield等于20,那么y2的yield等于x*z=20*2=40
val2 = it2.next(val1*5).value;//执行y2,给y1的yield赋值200(val1=40,*5=200),it2的next值:x=y1的yield等于200,那么y2的yield等于x*z=200*3=600

it1.next(val2/2);//val2/2的值会返回给it1.y2的yield并赋值给y,log打印20 300 3
it2.next(val1/4);//val1/4的值会返回给it2.y2的yield并赋值给y,log打印200 10 3

这就是所谓的协同操作,但是工序各不相干!
在这里插入图片描述

for…of 循环

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

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
function* fibonacci() {
  let [prev, curr] = [1, 1];
  while(true){
    [prev, curr] = [curr, prev + curr];
    yield curr;
  }
}

for (let n of fibonacci()) {
  if (n > 10000000) break;
  console.log(n);
}

Generator.prototype.return()

Generator 函数返回的遍历器对象,还有一个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 },中介并使后面的调用无效 
g.next()        // { value: undefined, done: true }

yield*

如果在 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"

foobar都是 Generator 函数,在bar里面调用foo,是不会有效果的。

这个就需要用到yield*表达式,用来在一个 Generator 函数里面执行另一个 Generator 函数。

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"

再来看一个对比的例子。

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"

上面例子中,outer2使用了yield*outer1没使用。结果就是,outer1返回一个遍历器对象,outer2返回该遍历器对象的内部值。

从语法角度看,如果yield表达式后面跟的是一个遍历器对象,需要在yield表达式后面加上星号,表明它返回的是一个遍历器对象。这被称为yield*表达式。

  • 作为对象属性的 Generator 函数

如果一个对象的属性是 Generator 函数,可以简写成下面的形式。

let obj = {
  * myGeneratorMethod() {
    ···
  }
};

那么这个和异步有什么关系呢?

  • 就是因为yield的暂停机制,可以在后一个得到结果后选择是否立马继续调用下一个yield后面的代码,这样,准确而有序的顺序加上可控的执行时间不正是异步所需要的吗?

Promise

什么是Promise?

你不知道的JavaScript中卷:
未来值–>设想一下这样一个场景:我走到快餐店的柜台,点了一个芝士汉堡。我交给收银员1.47美元,通过付款下单,发出了一个队某值"汉堡"的请求。我已经启动了一次交易;

但是,通常我不能立马得到一个芝士汉堡。收银员会交给我一个东西作为替代:一张带有订单号的收据。订单号就是一个IOU(I owe you)Promise,保证最终我能或得这个汉堡。

所以我好好的保留这个收据,这代表我的汉堡。在等待的过程中我可以做很多其他的事情,比如打个电话邀请其他朋友来一起聚一聚,看一份报纸,和周围的人打个招呼等;


这个过程里我的头脑里都会渴望着这个芝士汉堡,尽管还没有拿到手,但是芝士汉堡就像占位符一样存在我的大脑里。从本质上讲,这个占位符使得这个值不再依赖时间,这是个未来值。


终于,我听到收银员喊了"订单113号",然后愉快地拿着收据走到收银台领取了自己的芝士汉堡。但是也有一种情况,就是在等待这个时间里,芝士恰好在前一个人的时候用完了,那么我不得不得选择另外一种或者其他的解决办法,也就是未来值的特性:可能成功,也可能失败!

  • Promise有三种状态pending(正在),fulfilled(成功),reject(已经失败);但是关注的结果只有成功和失败,因为正在进行的Promise对象是无法被操作的。正作为开弓没有回头箭。

  • Promise和Ajax的代码结构很类似,链式反应,有成功或者错误的返回结果,主体进行值或者某些处理,然后后面会得到结果,选择是否还有进一步的执行,就像这样的伪代码:

Promise((resolve,reject)=>{
	//一系列需要等待结果的操作
	//Promise是同步的,在script标签里会立即执行,饭后通过里面的操作返回给后面的操作方法。
	//resolve代表成功,会把结果返回到data,而reject返回到err
	//如果Promise.resolve(1).then(data=>{})那么then里会立刻接收到值为1的值,并进行'{}'里的代码,同样的如果直接调用reject就会直接返回给后面的err
	//resolve是Promise会默认传递进来的参数,主动调用触发成功和失败,但是通常不会主动触发, 都是让程序执行结果去决定该返回什么结果。
	//如果主动调用,resolve和reject里的参数就是后面data和err接收到的值
})
.then((data,err)=>{
	//Promise里的操作成功,会把里面返回的结果传递到data里,前提是有返回结果,不然就是一个执行成功的状态,可以出发then里的代码,但没有数据的交互。
	//err是没有按照预期的执行方向走,返回了异常,可能是代码出错,也可能是其他方面的错误。
	//或者这里的两个参数改成response/resoleved和reject会更加专业,它们代表反馈/接受 和 拒绝
})
.then()
.catch()
//当然,链式反应可以继续进行下去,也可以用catch捕获错误,这里只是做简单介绍,不做详细介绍
  • then的接受和拒绝
    then会接收前面返回的Promise对象,这个对象一定会包括成功或错误两个结果之一,那么then里可以怎么处理,或者说怎么写对结果的处理呢?
//第一种,直接将两种状态解构出来
then((res,rej)=>{
	function(res){
		//对成功的处理
	}
	function(rej){
		//对失败的处理
	}
})
//第二种,隐式的解构
then(
	function(res){
		//对成功的处理
	},
	function(rej){
		//对失败的处理
	}
)

包装Promise的作用:避免回调地狱,过多的嵌套和回调会使程序有极差的易读性和维护性。

Promise.prototype.catch()

Promise.prototype.catch方法是.then(null, rejection)的别名,用于指定发生错误时的回调函数。

p1()
  .then(function(data){
    console.log(data)
  })
  .catch(function(err){
  	console.log(err)
  })
//reject不能结束Promise
//>5,走reject 	

协同者Promise.all()

Promise.all方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。

const p = Promise.all([p1, p2, p3]);

p的状态由p1p2p3决定,分成两种情况。

  1. 只有p1p2p3的状态都变成fulfilledp的状态才会变成fulfilled。 此时p1p2p3的返回值组成一个数组,传递给p的回调函数。
  2. 只要p1p2p3之中有一个被rejectedp的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。

promises是包含 3 个 Promise 实例的数组,只有这 3 个实例的状态都变成fulfilled,或者其中有一个变为rejected,才会调用Promise.all方法后面的回调函数。

如果作为参数的 Promise 实例,自己定义了catch方法,那么它一旦被rejected,并不会触发Promise.all()catch方法,如果没有参数没有定义自己的catch,就会调用Promise.all()catch方法。

//如果所有promise对象都成功了,返回成功的所有结果组成的数组
let p = Promise.all([
    new Promise((resolve,reject)=>{
        resolve("1")
    }),
    new Promise((resolve,reject)=>{
        resolve("2")
    }),
    new Promise((resolve,reject)=>{
        resolve("3")
    })
])
.then( data =>{
    console.log(data); //["1","2","3"]
})
.catch( err =>{
    console.log(err);
})
//如果又一个没有成功,则返回第一个失败的,后面不管什么结果都返回那个失败的结果,这叫猪队友法则
let p2 = Promise.all([
    new Promise((resolve,reject)=>{
        resolve("1")
    }),
    new Promise((resolve,reject)=>{
        reject("2")
    }),
    new Promise((resolve,reject)=>{
        reject("3")
    })
])
.then( data =>{
    console.log(data);
})
.catch( err =>{
    console.log(err); //2
})

竞争者Promise.race()

Promise.race方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。

const p = Promise.race([p1, p2, p3]);

上面代码中,只要p1p2p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。

let p = Promise.race([
    new Promise((resolve,reject)=>{
        setTimeout(()=>{
            resolve("100")
        },100)
    }),
    new Promise((resolve,reject)=>{
        setTimeout(()=>{
            resolve("200")
        },200)
    }),
    new Promise((resolve,reject)=>{
        setTimeout(()=>{
            resolve("99")
        },99)
    })
])
.then( data =>{
    console.log(data);//99
})
.catch( err =>{
    console.log(err);
})

Promise的resolve和reject

Promise.resolve()

有时需要将现有对象转为 Promise 对象,Promise.resolve方法就起到这个作用。

const jsPromise = Promise.resolve('123');

上面代码将123转为一个 Promise 对象。

Promise.resolve等价于下面的写法。

Promise.resolve('123')
// 等价于
new Promise(resolve => resolve('123'))

Promise.resolve方法的参数分成四种情况。

  1. 参数是一个 Promise 实例

    如果参数是 Promise 实例,那么Promise.resolve将不做任何修改、原封不动地返回这个实例。

  2. 参数是一个thenable对象

    thenable对象指的是具有then方法的对象,比如下面这个对象。

    let thenable = {
      then: function(resolve, reject) {
        resolve(42);
      }
    };
    
    

    Promise.resolve方法会将这个对象转为 Promise 对象,然后就立即执行thenable对象的then方法。

    let thenable = {
      then: function(resolve, reject) {
        resolve(42);
      }
    };
    
    let p1 = Promise.resolve(thenable);
    p1.then(function(value) {
      console.log(value);  // 42
    });
    
    

    上面代码中,thenable对象的then方法执行后,对象p1的状态就变为resolved,从而立即执行最后那个then方法指定的回调函数,输出 42。

  3. 参数不是具有then方法的对象,或根本就不是对象

    如果参数是一个原始值,或者是一个不具有then方法的对象,则Promise.resolve方法返回一个新的 Promise 对象,状态为resolved

    const p = Promise.resolve('Hello');
    
    p.then(function (s){
      console.log(s)
    });
    // Hello
    
    

    上面代码生成一个新的 Promise 对象的实例p。由于字符串Hello不属于异步操作(判断方法是字符串对象不具有 then 方法),返回 Promise 实例的状态从一生成就是resolved,所以回调函数会立即执行。Promise.resolve方法的参数,会同时传给回调函数。

  4. 不带有任何参数

    Promise.resolve方法允许调用时不带参数,直接返回一个resolved状态的 Promise 对象。

    所以,如果希望得到一个 Promise 对象,比较方便的方法就是直接调用Promise.resolve方法。

    const p = Promise.resolve();
    
    p.then(function () {
      // ...
    });
    
    

    上面代码的变量p就是一个 Promise 对象。

    需要注意的是,立即resolve的 Promise 对象,是在本轮“事件循环”(event loop)的结束时,而不是在下一轮“事件循环”的开始时。

    setTimeout(function () {
      console.log('three');
    }, 0);
    
    Promise.resolve().then(function () {
      console.log('two');
    });
    
    console.log('one');
    
    // one
    // two
    // three
    
    

    上面代码中,setTimeout(fn, 0)在下一轮“事件循环”开始时执行,Promise.resolve()在本轮“事件循环”结束时执行,console.log('one')则是立即执行,因此最先输出。

    Promise.reject()

    Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected

    const p = Promise.reject('出错了');
    // 等同于
    const p = new Promise((resolve, reject) => reject('出错了'))
    
    p.then(null, function (s) {
      console.log(s)
    });
    // 出错了
    
    

    上面代码生成一个 Promise 对象的实例p,状态为rejected,回调函数会立即执行。

    注意,Promise.reject()方法的参数,会原封不动地作为reject的理由,变成后续方法的参数。这一点与Promise.resolve方法不一致。

    const thenable = {
      then(resolve, reject) {
        reject('出错了');
      }
    };
    
    Promise.reject(thenable)
    .catch(e => {
      console.log(e === thenable)
    })
    // true
    
    

    上面代码中,Promise.reject方法的参数是一个thenable对象,执行以后,后面catch方法的参数不是reject抛出的“出错了”这个字符串,而是thenable对象。

对比一次主动的resolve和reject:

首先是resolve和reject的执行顺序问题:

new Promise((resolve,reject)=>{
    console.log("第一行");
    console.log("第二行");
    resolve("直接成功返回数据,在第三行");
    console.log("第四行");
}).then((res,rej) => {
    console.log(res);
})

/* 
第一行
第二行
第四行
直接成功返回数据, 在第三行
*/
new Promise((resolve,reject)=>{
    console.log("第一行");
    console.log("第二行");
    reject("直接错误返回消息数据,错误提示就是本消息,在第三行");
    console.log("第四行");
}).then((res,rej) => {
    // console.log(res);
    console.log(rej);
    
})

在这里插入图片描述

resolve和reject都会在当前Promise对象里所有代码执行完成的最后执行,并且印证了参数是直接传递到then的两个参数里的结果。

当然我们也可以这样直接调用,虽然在实际开发中没有意义:
Promise.resolve(1).then(data=>{ })这样就会直接调用then里的箭头函数,reject也一样。

async 函数

含义

ES2017 标准引入了 async 函数,使得异步操作变得更加方便。

async 函数是 Generator 函数的语法糖。

什么是语法糖?

意指那些没有给计算机语言添加新功能,而只是对人类来说更“甜蜜”的语法。语法糖往往给程序员提供了更实用的编码方式,有益于更好的编码风格,更易读。不过其并没有给语言添加什么新东西。

反向还有语法盐:

主要目的是通过反人类的语法,让你更痛苦的写代码,虽然同样能达到避免代码书写错误的效果,但是编程效率很低,毕竟提高了语法学习门槛,让人齁到忧伤。。。

明面上async函数使用时就是将 Generator 函数的星号(*)替换成async,将yield替换成await,仅此而已。

async函数对 Generator 函数的区别:

(1)内置执行器。

Generator 函数的执行必须靠执行器,而async函数自带执行器。也就是说,async函数的执行,与普通函数一模一样,只要一行。如果你是从上面顺着看下来的,我敬你,这里的执行器就是Generator和Iterator的yield和next机制,不用怀疑!

(2)更好的语义。

asyncawait,比起星号和yield,语义更清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。

(3)正常情况下,await命令后面是一个 Promise 对象。如果不是,会被转成一个立即resolve的 Promise 对象。

(4)返回值是 Promise。

async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用then方法指定下一步的操作。

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

执行顺序

  • 首先在事件循环里,Promise是同步操作,异步的是then,但是因为Promise同步会立即执行,那么then会极大的收到Promise里的代码结果得到速度影响,而且优先级高于延时函数执行,可以回看我的事件循环译文。
  • 在Generator函数里,执行循序按照yield进行,并依靠返回的Generator对象进行next执行,而Async函数的await不会暂停,它会自动等待上一步的结果,并在上一步出结果的时候自动‘next’。
async function fn(a) {
    console.log(0);
    let b = a;
    console.log(b);
    await new Promise((res, rej) => {
        setTimeout(() => {
            res("2")
        }, 2000);
    }).then(data => console.log(data));
    console.log(3);
}
fn(1)

在这里插入图片描述

  • 非常完美,在这个async异步函数里,不管同步异步代码都按照自己的顺序正常的执行下去了,就像普通函数一样fn调用传参,不用next,这就是async的便捷。你什么时候完成我await就什么时候往下!

  • 再比如我们的数据交互,实际操作可能会有不一样:
    下面是一个ajax的伪代码:

async function fn() {
    let msg = await new Promise((res, rej) => {
        $.ajax({
            success(result) {
                res(result)
            }
        })
    })
    console.log(msg);
}
fn()

这样我们就能100%地拿到数据并传递出来。

错误处理

如果await后面的异步操作出错,那么等同于async函数返回的 Promise 对象被reject

async function f() {
  await new Promise(function (resolve, reject) {
    throw new Error('出错了');
  });
}

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

上面代码中,async函数f执行后,await后面的 Promise 对象会抛出一个错误对象,导致catch方法的回调函数被调用,它的参数就是抛出的错误对象。具体的执行机制,可以参考后文的“async 函数的实现原理”。

防止出错的方法,也是将其放在try...catch代码块之中。

async function f() {
  try {
    await new Promise(function (resolve, reject) {
      throw new Error('出错了');
    });
  } catch(e) {
  }
  return await('hello world');
}

如果有多个await命令,可以统一放在try...catch结构中。

async function main() {
  try {
    const val1 = await firstStep();
    const val2 = await secondStep(val1);
    const val3 = await thirdStep(val1, val2);

    console.log('Final: ', val3);
  }
  catch (err) {
    console.error(err);
  }
}

应用

var fn = function (time) {
  console.log("开始处理异步");
  setTimeout(function () {
    console.log(time);
    console.log("异步处理完成");
    iter.next();
  }, time);

};

function* g(){
  console.log("start");
  yield fn(3000)
  yield fn(500)
  yield fn(1000)
  console.log("end");
}

let iter = g();
iter.next();

下面是async函数的写法

var fn = function (time) {
  return new Promise(function (resolve, reject) {
    console.log("开始处理异步");
    setTimeout(function () {
      resolve();
      console.log(time);
      console.log("异步处理完成");
    }, time);
  })
};

var start = async function () {
  // 在这里使用起来就像同步代码那样直观
  console.log('start');
  await fn(3000);
  await fn(500);
  await fn(1000);
  console.log('end');
};

start();

写在最后

  • 把Generator写在最前面是因为内容包含,这三个只是点记录在一起是因为息息相关。
  • 在实际的开发中,await里添加各种异步代码可能会影响执行效率,但是解决异步的办法不止一种,async和Promise结合却又独到的优点,顺序的执行效果,有条不紊的执行顺序,完全掌控的执行是某些方式无法替代的。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

奔跑的飞牛

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值