异步函数有哪些:
- setTimeOut、setInterval
- Promise
- 生成器(Generators)
- async await
@异步函数的执行过程:
- 【调用异步函数】回调函数 等待执行中。。。。。。
- 【开始执行回调】主线程同步任务执行完成的时间+setTimeout延迟的时间,过后开始执行异步函数的回调函数(所以定时器的时间是不准确的)
- 【回调执行完成】回调函数执行完成
@异步函数的特点:
- 回调执行完算是真正的执行完成。
- 控制顺序必须在回调中控制与其他程序的执行顺序
- 等待------> 开始执行回调 -------->回调执行完成
一,setTimeout
案例一:
@此方式代码可读性很差
function setp1() {
setTimeout(() => {
console.log('执行1')
setp2('1传递的参数')
}, 2000);
}
function setp2(data) {
setTimeout(() => {
console.log('执行2', data)
setp3('2传递的参数')
}, 2000);
}
function setp3(data) {
setTimeout(() => {
console.log('执行3', data)
}, 2000);
}
setp1();
@输出:
执行1
执行2 1传递的参数
执行3 2传递的参数
案例二:
需求:1执行完,执行2; 3执行完执行4; 2和4都执行完 执行5;
setTimeout中毫无意义的flag,只能调用一个函数的情况下,加不加flag,很显然顺序是保证的。
function step1(){
setTimeout(function(){
console.log("执行 step1中的异步回调");
//step1异步函数执行完要执行step2异步函数
var data=['step1传递的数据']
step2(data);
},2000)
};
var step2CallBackFlag;
function step2(data){
step2CallBackFlag=false;
setTimeout(function(){
console.log("执行 step2中的异步回调",data);
//step2异步函数执行完要执行step3异步函数
var data2=['step2传递的数据']
step3(data2);
step2CallBackFlag=1;
},2000)
};
function step3(data){
setTimeout(function(){
console.log("执行 step3中的异步回调",data);
step4(['step3传递的数据'])
},2000)
};
function step4(data){
var step4CallBackFlag=false;
setTimeout(function(){
console.log("执行 step4中的异步回调",data);
step4CallBackFlag=1;
if(step2CallBackFlag && step4CallBackFlag){step5()}
},2000)
};
function step5(data){
setTimeout(function(){
console.log("执行 step5中的异步回调",data);
},2000)
};
step1()
二、Promise
@案例一
function step1() {
return new Promise(function (resolve, reject) {
console.log('执行1')
setTimeout(() => {
console.log('1请求结束')
resolve('1传递的参数')
}, 4000);
});
}
function step2(data) {
return new Promise(function (resolve, reject) {
console.log('执行2 ,接收的参数', data)
setTimeout(() => {
resolve('2传递的参数')
}, 4000);
});
}
function step3(data) {
return new Promise(function (resolve, reject) {
console.log('执行3 ,接收的参数', data)
setTimeout(() => {
resolve('3传递的参数')
}, 4000);
});
}
step1().then(step2).then(step3).then(res => {
console.log('最后', res)//3传递的参数
});
VM295:3 执行1
Promise {<pending>}
VM295:5 1请求结束
VM295:12 执行2 ,接收的参数 1传递的参数
VM295:20 执行3 ,接收的参数 2传递的参数
VM295:27 最后 3传递的参数
@案例二
let love = new Promise((resolve, reject) => {
setTimeout(() => { //开始谈恋爱,不过恋爱的结果要以后才知道
let happy = Math.random() >= 0.3 ? true : false
if ( happy ) {
resolve('marry') //恋爱成功,决定结婚
} else {
reject('break') //恋爱失败,决定分手
}
}, 500)
})
love.then(result => {
console.log(result) //处理恋爱成功的回调,result是上面resolve传过来的'marry'
}).catch(result => {
console.log(result) //处理恋爱失败的回调,result是上面reject传过来的'break'
})
一个简单却完整的Promise的例子。需要特别注意的是,Promise在经过pending状态达到成功或失败状态时就会凝固,即到达成功状态后再也不会失败,失败以后也不会回到成功状态。
@案例三
所以下面的Promise一定是失败状态的,即便reject后面跟了resolve也没用。正所谓:若爱,请深爱,若弃,请彻底,不要暧昧,伤人伤己。柏拉图这话,说的就是Promise的状态凝固。
let love = new Promise((resolve, reject) => {
reject('break')
resolve('marry')
})
love.then(result => {
console.log(result)
}).catch(result => {
console.log(result)
})
三、生成器(Generators)
function* main() {
var result = yield step1("执行开始");
var data2 = yield step2(result);
var data3 = yield step3(data2);
console.log('执行结束', data3);
//do 别的ajax请求;
}
function step1(msg) {
setTimeout(() => {
console.log('第一个请求 compile', msg),
it.next('第一个请求 result');
}, 2000)
}
function step2(msg) {
setTimeout(() => {
console.log('第二个请求 compile', msg),
it.next('第二个请求 result');
}, 2000)
}
function step3(msg) {
setTimeout(() => {
console.log('第三个请求 compile', msg),
it.next('第三个请求 result');
}, 2000)
}
var it = main();
it.next();
console.log("执行到这儿啦");
当你调用一个generator时,它将返回一个迭代器对象。这个迭代器对象拥有一个叫做next的方法来帮助你重启generator函数并得到下一个值。next方法不仅返回值,
执行结果
四、async await
async函数返回一个promise对象,
async 函数内部 return 返回的值。会成为then方法回调函数的参数
async function f() {
return 'hello world'
};
f().then( (v) => console.log(v)) // hello world
如果 async
函数内部抛出异常,则会导致返回的 Promise 对象状态变为 reject
状态。抛出的错误而会被 catch
方法回调函数接收到。
async function e(){
throw new Error('error');
}
e().then(v => console.log(v))
.catch( e => console.log(e));//Error: error
async 函数返回的 Promise 对象,必须等到内部所有的 await 命令的 Promise 对象执行完,才会发生状态改变
也就是说,只有当 async
函数内部的异步操作都执行完,才会执行 then
方法的回调。
delay中的promise必须不是resolve的状态,否则就不会延迟等待6s,而是立即执行。应该保持Pending状态。
var delay = timeout => new Promise(resolve=> setTimeout(resolve, timeout));
async function f(){
await delay(1000);
await delay(2000);
await delay(3000);
return 'done';
}
f().then(v => console.log(v))
.then(v => console.log(v))
.then(v => console.log(v)); // 等待6s后才输出 'done' 和 两个 undefined,//不会报错
正常情况下,await 命令后面跟着的是 Promise ,如果不是的话,也会被转换成一个 立即 resolve 的 Promise
如下面这个例子:
async function f() {
return await 1
};
f().then( (v) => console.log(v)) // 1复制代码
如果返回的是 reject 的状态,则会被 catch
方法捕获。
Async 函数的错误处理
async
函数的语法不难,难在错误处理上。
先来看下面的例子:
let a;
async function f() {
await Promise.reject('error');
a = await 1; // 这段 await 并没有执行
}
f().then(v => console.log(a));
如上面所示,当 async
函数中只要一个 await
出现 reject 状态,则后面的 await
都不会被执行。
解决办法:可以添加 try/catch
。
// 正确的写法
let a;
async function correct() {
try {
await Promise.reject('error')
} catch (error) {
console.log(error);
}
a = await 1;
return a;
}
correct().then(v => console.log(a)); // 1
如果有多个 await
则可以将其都放在 try/catch
中。
@特别感谢本文作者的付出:
链接:https://juejin.cn/post/6844903487805849613
@总结与回顾
- 异步函数的执行过程(等待------> 开始执行回调 -------->回调执行完成)
- async await优于promise,promise优于普通异步操作,比如ajax
- async 函数是promise和 Generators的语法糖
- 往往错误处理,更好的方法是我们通过包装await 后的promise,让await可以直接返回错误信息,比如 [err,data]=await wrap(promise)