异步函数的执行顺序控制

5,460 阅读6分钟

异步函数有哪些:

  1.  setTimeOut、setInterval
  2. Promise
  3. 生成器(Generators)
  4. async await

@异步函数的执行过程:

  1. 【调用异步函数】回调函数 等待执行中。。。。。。
  2. 【开始执行回调】主线程同步任务执行完成的时间+setTimeout延迟的时间,过后开始执行异步函数的回调函数(所以定时器的时间是不准确的)
  3. 【回调执行完成】回调函数执行完成


@异步函数的特点:

  1. 回调执行完算是真正的执行完成。
  2. 控制顺序必须在回调中控制与其他程序的执行顺序
  3. 等待------> 开始执行回调 -------->回调执行完成

一,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)

生成器( generator)是能返回一个迭代器的函数。 生成器函数也是一种函数,最直观的表现就是比普通的function多了个星号*,在其函数体内可以使用yield关键字,函数会在每个yield后暂停,等待,直到这个生成的对象,调用下一个next(),每调用一次next 会往下执行一次yieId,然后暂停

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 中。




@特别感谢本文作者的付出:

链接:juejin.cn/post/684490…

链接:https://juejin.cn/post/6844903487805849613

@总结与回顾

  • 异步函数的执行过程(等待------> 开始执行回调 -------->回调执行完成)
  • async await优于promise,promise优于普通异步操作,比如ajax
  • async 函数是promise和 Generators的语法糖
  • 往往错误处理,更好的方法是我们通过包装await 后的promise,让await可以直接返回错误信息,比如 [err,data]=await wrap(promise)