万字长文,请根据右侧目录查阅所需!
Generator
打破运行,生成器,控制运行迭代器
基本概念
- generator本身并不是用于处理异步的,但是能够实现!!!
Generator函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。
执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。
跟普通函数的区别
function
关键字与函数名之间有一个星号–>"*";- 函数体内部使用
yield
表达式,定义不同的内部状态。 - Generator函数不能跟new一起使用,会报错。
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
上面代码定义了一个 Generator 函数helloWorldGenerator
,它内部有两个yield
表达式(hello
和world
),即该函数有三个状态: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"
foo
和bar
都是 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
的状态由p1
、p2
、p3
决定,分成两种情况。
- 只有
p1
、p2
、p3
的状态都变成fulfilled
,p
的状态才会变成fulfilled
。 此时p1
、p2
、p3
的返回值组成一个数组,传递给p
的回调函数。 - 只要
p1
、p2
、p3
之中有一个被rejected
,p
的状态就变成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]);
上面代码中,只要p1
、p2
、p3
之中有一个实例率先改变状态,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
方法的参数分成四种情况。
-
参数是一个 Promise 实例
如果参数是 Promise 实例,那么
Promise.resolve
将不做任何修改、原封不动地返回这个实例。 -
参数是一个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。 -
参数不是具有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
方法的参数,会同时传给回调函数。 -
不带有任何参数
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)更好的语义。
async
和await
,比起星号和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结合却又独到的优点,顺序的执行效果,有条不紊的执行顺序,完全掌控的执行是某些方式无法替代的。