在学ES6的异步编程解决方案时,知道Promise对象,Generator函数与async函数都是ES6为解决异步编程的提供的方案,但在使用时有点懵,so,当然是写写写来帮助巩固知识点和理清思路了~
(写此文章作为我的初学记录,或许以后再回来翻阅时会有不同感想感受与收获hiahiahia~)
一、Promise
promise是一个对象,用于获取异步操作的消息;promise也是一个容器,里面放着某个异步操作的结果。
promise对象思想:每一个异步操作返回一个promise对象,这个promise对象有一个then方法,允许指定回调函数
1.promise对象的两大特点
(1)对象的三个状态不受外部影响
三种状态说明:
- pending(进行中)
- fulfilled(已成功)
- rejected(已失败)
只有异步操作结果可以决定当前是什么状态
(2)状态一旦改变就不会在变
pending->fulfilled
pending->rejected
状态改变之后,添加回调监听,也会立即得到这个结果
promise对象是一个构造函数,用来生成promise实例
const promise = new Promise(
function( resolve, reject ) {
if(异步操作成功){
resolve(value);
}else{
reject(error);
}
}
)
构造函数的参数是一个函数,这个函数有两个参数,分别代表这个实例成功或失败之后的回调函数,也就是:
- resolve函数的作用:将状态pending->resolved,在异步操作成功时调用,并将返回的异步结果(可能是一个promise对象)作为参数传递出去;
- reject函数的作用:将状态pending->rejected,在异步操作失败时调用,并将异步操作报出的错误作为参数传递出去(抛出错误);
2.Promise对象常用的回调方法
- then()
为promise实例添加状态改变时的回调函数,不管成功或失败(resolved/rejected),只要状态变了就回调 - catch()
用于指定发生错误时的回调函数(捕捉执行过程中抛出的错误进行处理)
一般来说,不要在then()里面定义reject状态的回调函数,也就是说,尽量使用catch方法来捕获错误,如:
promise.then(function(value){}).catch(function(err){})
// 这样可以捕获前面then方法(或许又返回一个promise实例)执行中的错误
而不是:
promise.then(function(value){} , function(err){} )
其实也就是: .then(null, rejectoin) 或 .then(undefined, rejection)
- finally()
用于指定不管promise对象最后的状态如何,都会执行的操作
finally方法的回调函数不接收任何参数,可以把在then中状态为resolved和rejeted时返回的结果,写到finally中,这样就只用写一次了;
如:
promise.then( result => {return result;}, error => {throw error;} )
//等同于
promise.finally(() => {});
- all()
将多个promise实例,包装成一个新的promise实例,如:
const p = Promise.all([p1,p2,p3])
//p1,p2,p3都是promise实例
//p1,p2,p3的状态都为fulfiled时,p的状态才为fulfiled,且p1,p2,p3的返回值会组成一个数组,传给p的回调函数resolve
//p1,p2,p3只要有一个被rejected,p的状态就是rejected,且第一个被rejected的实例的返回值会传给p的回调函数reject
- race()
同样将多个promise实例,包装成一个新的promise实例
const p = Promise.race([p1,p2,p3])
//p1,p2,p3都是promise实例,只要有一个实例改变了状态,p的状态也会跟着改变,且率先改变的实例的返回值,会传递给p的回调函数
- resolve()
可以将现有对象转为一个新的promise对象
Promise.resolve()方法的参数情况:
- 参数是一个promise实例:不做任何修改,返回这个实例
- 参数是一个thenable对象(指的是具有then方法的对象):将这个对象转为promise对象,然后立即执行这个对象的then方法
- 参数不是对象,或是不具有then方法的对象:Promise.resolve()方法返回一个新的promise对象,状态为resolved
- 不带任何参数:直接返回一个resolved状态的promise对象 ;const p = Promise.resolved;
- reject()
也可以将现有对象转为一个新的promise实例,这个实例的状态为rejected,如:
cosnt p = Promise.reject('error')
//等同于
const p = new Promise((resolve, reject) => reject('error'));
~
几个注意点:
then,catch,finally方法定义在原型对象Promise.prototype上,即Promise实例具有这些方法;
then方法指定的回调函数,在当前脚本所有同步任务执行完毕之后才会执行。
二、Generator函数
Generator函数是一个状态机,内部封装了多个内部状态,这些内部状态由yield表达式或return语句来定义;
也是一个遍历器生成函数,返回一个遍历器对象,也就是指向内部状态的指针对象,可以遍历generator函数内部的每一个状态;
1.使用
调用generator函数,返回一个遍历器对象,代表generator函数的内部指针;下一步,使用next()方法,使内部指针移向下一个内部状态(yield表达式或return语句)
每次调用next()方法,都会返回一个带有value和done属性的对象,value代表当前内部状态的值(也就是yiled表达式后面那个表达式的值,或return返回的值),done是一个布尔值,表示当前遍历是否结束;
换言之,Generator函数是分段执行的,yield表达式是暂停的标志,next()可以恢复执行;
//定义一个generator函数
function* test(){
yield "hello!";
yield "你好!";
return "结束了~";
}
let t = test();
t.next(); //调用generator函数test(),执行它的next方法,返回"hello!"
//如果第一个就是return语句,后面再t.next()也只是返回undefined(value值)和true(done值)了
2.内部状态
yield表达式:可以执行多个yield表达式,返回一系列的值
return语句:不具备位置记忆功能,且一个函数只能有一个return语句,就是只能执行一次return
3.yield表达式
另外,需要注意,yield表达式如果放在另一个表达式中,必须加上圆括号;若是作为参数或放在表达式右边(=号右边),可以不加()
遇到yield表达式时,就暂停执行后面的操作,并将紧跟在yield后面的表达式的值,作为返回的对象的value属性值;
function* test(){
console.log("我是" + (yield '123'));
yield "hello!";
yield "你好!";
return "结束了~";
}
//第一次调用next()时,只返回了value:'123',done:false,并不会返回"我是";
//第二次调用next()时,会返回"我是 undefined"和value:hello,done:false
4.与iterator接口的关系
一个对象的Symbol.iterator方法,等于这个对象的遍历器生成函数(刚好,generator函数就是一个遍历器生成函数),调用这个函数会返回这个对象的一个遍历器对象(也就是具有了iterator接口,可以使用for…of 遍历)
如普通对象就没有部署iterator,不能被遍历
obj[Symbol.iterator] = function* () {
var key = Object.keys(obj);//返回obj对象的key名的数组
for(let i=0;i<key.length;i++){ yield key[i]; }
}
5.next方法的参数
yield表达式本身没有值(总是返回undefined),next()的参数可以当作上一个yield表达式的值;这样就可以在generator函数运行的不同阶段向其内部注入不同的值,从而调整函数行为。
三、async函数
(asynchronous 异步的)
1.特点
- 使用async关键字声明函数;
- 默认返回一个已解决的promise对象
async表示函数内有异步操作;await表示紧跟在其后的表达式需要等待结果。
async函数返回一个Promise对象,可以使用then方法添加回调函数;而Generator函数返回一个Iterator对象。
(在写法上,与Generator函数的区别就是:把*换成async,把yield换成await;async 函数的实现原理,就是将 Generator 函数和自动执行器,包装在一个函数里。)
await只能在async函数中使用,名为“等待”,await命令后是一个promise对象,如果不是,会被转成一个resolve的promise对象。
如果await后面的promise状态是reject的话会抛出异常,所以可以将await语句写在try catch里。
当async函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句,所以调用async函数虽然有等待, 但是并不会导致阻塞, 因为它内部的所有阻塞都封装在promise对象中异步执行。
举个栗子:
function resolveAfter2Seconds(x) {
return new Promise(resolve => {
setTimeout(() => {
resolve(x);
}, 2000);
});
}
async function add1(x) {
const a = await resolveAfter2Seconds(20);
const b = await resolveAfter2Seconds(30);
const {c} = await { c: 10}
return x + a + b + c;
}
add1(10).then(v => {
console.log(v); // prints 70 after 4 seconds.
});
async函数内置执行器,与普通函数一样调用执行;Generator函数需要依靠next()函数执行。
2.语法
-
async函数返回一个Promise对象(一般地,await后面就是一个Promise对象,返回这个对象的结果;若不是一个对象,则返回相应的值)
-
函数内部return的值会作为then方法回调函数的参数
-
若内部抛出错误,会使Promise对象变为reject状态,且抛出的错误对象(如:throw new
Error(‘error’))会被catch方法回调函数接收到 -
若await后面的对象变为reject状态,那整个async函数都会中断执行,如:
async function f() {
await Promise.reject('出错了');
await Promise.resolve('hello world'); // 不会执行
}
3.错误处理
await后面的异步操作出错,表明async函数返回的Promise对象被reject;若是希望在一个异步操作失败之后,仍要执行后面的异步操作,可:
(1)使用try…catch
如将第一个await放入try…catch中,那么不管这个异步操作是否成功,第二个await都会执行;
可以在try…catch外面使用for循环,若异步操作成功,则break退出循环;若失败,会被catch捕捉,进入下一次循环,如:
const superagent = require('superagent');
const NUM_RETRIES = 3;
async function test() {
let i;
for (i = 0; i < NUM_RETRIES; ++i) {
try {
await superagent.get('http://google.com/this-throws-an-error');
break;
} catch(err) {}
}
console.log(i); // 3
}
test();
(2)使用catch方法
在第一个await的Promise对象后面添加catch方法处理可能出现的错误,第二个await同样会执行
In brief,Generator与async相比较于Promise对象(的链式调用),写法上更简洁、清晰、明了,传参也更加方便~