参考
https://blog.csdn.net/tcy83/article/details/80427195
https://segmentfault.com/a/1190000007535316#articleHeader5
https://segmentfault.com/a/1190000007032448#articleHeader11
由于JavaScript是单线程的,因此异步编程是非常重要的,在异步操作中有一类很棘手的问题,被称为“回调地狱”,异步的回调函数中,又有另外的异步的回调,一个套一个,再参杂一些同步逻辑判断,代码就会变的难以维护。
//回调地狱
setTimeout(function(){
console.log("1 type");
setTimeout(function(){
console.log("2 type");
setTimeout(function(){
console.log("3 type");
...
},1000)
},1000)
},1000);
解决方法:
1.promise
ES6中的Promise对象用于异步操作,它表示一个尚未完成且预计在未来完成的异步操作。
有三种状态
- pending:初始值,不是fulfilled,也不是rejected
- fulfilled:代表操作成功 对应resolve
- rejected:代表操作失败
状态只能从pending转变为fulfilled,或者从pending转变为rejected。且一旦改变就不会再变了。
var p = new Promise(function(resolve,reject){
if (/* 异步操作成功 */) {
resolve(data);
} else {
/* 异步操作失败 */
reject(error);
}
}
p.then(function(value) {
// resolved
}, function(error) {
// rejected
});
resolve(data):在异步操作成功时调用,并将异步操作的结果,作为参数传递出去。
reject(data):在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
当状态发生变化,promise.then绑定的函数就会被调用。对应的,then方法有两个参数,分别实现resolved,rejected的回调方法,并接受Promise对象传出的值作为参数。
注意:Promise一旦新建就会「立即执行」,无法取消。
- 通过promise对象和then()的链式调用处理异步
function getPObj(num){
var p = new Promise(function(resolve,reject){
setTimeout(function(){
console.log("开始执行定时器:"+num);
resolve(num);
},2000);
});
return p;
}
getPObj(1).then(function(data){
console.log("我是回调方法");
console.log("执行回调方法:"+data);
return getPObj(2);
}).then(function(data){
console.log("我是回调方法");
console.log("执行回调方法:"+data);
return getPObj(3);
}).then(function(data){
console.log("我是回调方法");
console.log("执行回调方法:"+data);
});
- .catch()
有的时候我们会需要当回调方法为reject的时候,终止链条,就会用到.catch()方法,该方法是.then(undefined, onRejected)的别名。promise对象的错误,会一直向后传递,直到被捕获。即错误总会被下一个catch所捕获。then方法指定的回调函数,若抛出错误,也会被下一个catch捕获。catch中也能抛错,则需要后面的catch来捕获。
promise.then(function(data) {
console.log('success');
}).catch(function(error) {
console.log('error', error);
});
/*******等同于*******/
promise.then(function(data) {
console.log('success');
}).then(undefined, function(error) {
console.log('error', error);
});
//一般用法
sendRequest('test.html').then(function(data1) {
//do something
}).then(function (data2) {
//do something
}).catch(function (error) {
//处理前面三个Promise产生的错误
});
- .all()
var p = Promise.all([p1, p2, p3]);
该方法用于将多个Promise实例,包装成一个新的Promise实例。
-
Promise.all方法接受一个数组(或具有Iterator接口)作参数,数组中的对象(p1、p2、p3)均为promise实例(如果不是一个promise,该项会被用Promise.resolve转换为一个promise)。它的状态由这三个promise实例决定。
-
当p1, p2, p3状态都变为fulfilled,p的状态才会变为fulfilled,并将三个promise返回的结果,按参数的顺序(而不是 resolved的顺序)存入数组,传给p的回调函数.
当p1, p2, p3其中之一状态变为rejected,p的状态也会变为rejected,并把第一个被reject的promise的返回值,传给p的回调函数。
-
.rase()
var p = Promise.race([p1, p2, p3]);
该方法同样是将多个Promise实例,包装成一个新的Promise实例。
Promise.race方法同样接受一个数组(或具有Iterator接口)作参数。当p1, p2, p3中有一个实例的状态发生改变(变为fulfilled或rejected),p的状态就跟着改变。并把第一个改变状态的promise的返回值,传给p的回调函数。
注意,当race()方法包装的promise实例状态改变后,不会影响其他promise的执行。
2.generator
- generator(生成器)是ES6标准引入的新的数据类型。generator看上去像一个函数,但可以返回多次。
- 基本语法
function* text(n){
yield n + 1;
yield n + 2;
return n + 3;
}
generator由function*定义,通过yield可以多次返回
控制台输出一下发现,并没有输出123,而是输出了一个对象,这是因为test()只是创建了一个generator对象,还没有去执行它。
- 调用generator对象有两个方法:
1.通过generator对象的next()方法
next()方法可以接受参数,用来替换上一次yield执行的结果
2.通过for … of遍历
由于next()方法是通过ES6的迭代器接口[Symbol.iterator]来实现的,而for-of的原理就是每次遍历都会调用该对象的[Symbol.iterator]属性的next方法,当返回{value: undefined, done: true}后,表示遍历结束。
- generator的使用
通过yield和next()可以控制函数的执行与停止,用来处理异步编程。
//使用generator
function test1(sucess){
setTimeout(function(){
console.log("1 type");
sucess();
},1000)
}
function test2(sucess){
setTimeout(function(){
console.log("2 type");
sucess();
},1000)
}
function test3(sucess){
setTimeout(function(){
console.log("3 type");
sucess();
},1000)
}
function run(fn){
const gen = fn();
function next() {
const result = gen.next();
if (result.done) return;//结束
// result.value就是yield返回的值
result.value(next);//next作为入参,当前成功后,执行下一步
}
next();
};
//工序
function* task(){
yield test1;
yield test2;
yield test3;
}
run(task);//开始执行
3.async和await
-
async 是“异步”的简写,而 await 可以认为是 async wait 的简写。
-
async 用于申明一个 function 是异步的,而 await 用于等待一个异步方法执行完成。
-
await必须用在async声明的函数中
-
async
可以看到,这里async函数返回的是一个promise对象,async 函数(包含函数语句、函数表达式、Lambda表达式)会返回一个 Promise 对象,如果在函数中 return 一个直接量,async 会把这个直接量通过 Promise.resolve() 封装成 Promise 对象。
这里要注意,声明它是异步,形式是异步,但实际执行并没有异步。
结合 Promise 的特点——无等待,在没有 await 的情况下执行 async 函数,它会立即执行,返回一个 Promise 对象,不会阻塞后面的语句。这和普通返回 Promise 对象的函数相同。 -
await
await一般是在等待一个async函数执行结束的返回值,也就是promise对象。但是也不绝对,它可以等任意表达式的结果,所以,await 后面实际是可以接普通函数调用或者直接量的。
await 是个运算符,用于组成表达式,例如 “await test1()”,await 表达式的运算结果取决于它等待的东西。
-
如果它等到的不是一个 Promise 对象,那 await 表达式的运算结果就是它等到的东西。
-
如果它等到的是一个 Promise 对象,await 就忙起来了,它会阻塞后面的代码,等着 Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果。
-
async/await 的使用
假设一个业务,分多个步骤完成,每个步骤都是异步的,而且依赖于上一个步骤的结果。
function takeLongTime(n) {
return new Promise(resolve => {
setTimeout(() => resolve(n + 200), n);
});
}
function step1(n) {
console.log(`step1 with ${n}`);
return takeLongTime(n);
}
function step2(n) {
console.log(`step2 with ${n}`);
return takeLongTime(n);
}
function step3(n) {
console.log(`step3 with ${n}`);
return takeLongTime(n);
}
使用async/await
async function doIt() {
console.time("doIt");
const time1 = 300;
const time2 = await step1(time1);
const time3 = await step2(time2);
const result = await step3(time3);
console.log(`result is ${result}`);
console.timeEnd("doIt");
}
doIt();