Promise:
专门保证多个异步任务必须顺序执行,且解决了回调地狱问题的一项技术。
回调地狱问题:由于多层回调函数的嵌套,形成的很深的嵌套结构,就称之为回调地狱。
用回调函数的方式:
1.第一个任务把第二个任务装起来,暂不执行,第二个任务把第三个任务装起来,也不执行。
2.第一个任务最后一句话执行完毕,主动调用装有第二个任务的函数。
3.第二个任务最后一句话执行完毕,主动调用装有第三个任务的函数。
示例:使用回调函数解决多个异步任务顺序的执行
function liang(next){//亮哥:异步
console.log(`liang起跑...`);
setTimeout(function(){//异步:在主程序之外执行,主程序不会等它执行完
console.log(`liang到达终点!`);//亮最后执行的一句话
//之后,主动调用下一项任务
next();//下一项任务才开始执行
},6000)
}
function ran(next){//然哥:异步
console.log(`ran起跑...`);
setTimeout(function(){//异步: 在主程序之外,主程序和其他程序都不会等待它执行完
console.log(`ran到达终点!`);//然最后执行的一句话
//之后,主动调用下一项任务
next(); //下一项任务才开始执行
},4000)
}
function dong(){//东哥:异步
console.log(`dong起跑...`);
setTimeout(function(){//异步: 在主程序之外
console.log(`dong到达终点!`)
},2000)
}
//希望先亮跑,亮到达终点后,然跑,然到达终点后,东跑
//错误:
// liang();
// ran();
// dong();
//正确:
liang(
/*next=*/function(){
ran(
/*next=*/function(){
dong();
}
);//ran的next=function(){dong();}
}
);//liang的next=function(){ran();}
//当liang执行完最后一句话,之后自调用liang的next()
//其实就是调用function(){ran();},函数中的ran()开始执行
//当ran执行完最后一句话,之后自动调用ran的next()
//其实就是调用function(){dong();},函数中的dong()开始执行
何时用promise:只要多个异步函数必须顺序执行时,都用promise代替回调函数方式。
使用promise实现多个异步任务顺序执行,异步间任务传参,在前后两个异步任务之间传参,参数解构传多个变量。
如何:
(0). 所有异步函数不要加回调函数参数了!
(1). 用new Promise(function(door){ … })包裹住原来的异步函数代码。
强调: 原异步函数不需要做任何改变
(2). 在原异步函数最后执行的一句话之后,调用door开门!
结果: 自动执行.then()中串联的后续任务(下一个格子间,也可能是下一个函数)
(3). new Promise()是放在函数内创建的,所以,如果希望外部直到这里有一个格子间的对象可以串联,必须用return new Promise()将其返回到函数外部!
function 前一项异步函数(){
return new Promise(function(door){
原异步函数内容
异步函数最后一句话之后: door()
})
}
(4). 调用return new Promise()所在的异步任务函数,既可以执行异步函数的任务,又可以返回格子间,可用.then()与下一项任务串联
前一项任务().then(下一项任务)
//下一项任务不要加(),因为不是立刻执行!
(5). 多一个异步任务串联: 任务1().then(任务2).then(任务3)
示例:使用promise实现多个异步任务顺序执行
function liang(){//亮哥:异步
// 格子间 赠门
return new Promise(function(door){
console.log(`liang起跑...`);
setTimeout(function(){//异步
console.log(`liang到达终点!`);//亮最后执行的一句话
//之后,主动调用door开门!
door();//自动执行.then()中串联的下一项任务
},6000)
})
}
function ran(){//然哥:异步
// 格子间 门
return new Promise(function(door){
console.log(`ran起跑...`);
setTimeout(function(){//异步
console.log(`ran到达终点!`);//然最后执行的一句话
//之后,主动调用door
door(); //通知.then中串联的下一项任务可以开始执行!
},4000)
})
}
function dong(){//东哥:异步
console.log(`dong起跑...`);
setTimeout(function(){//异步: 在主程序之外
console.log(`dong到达终点!`)
},2000)
}
//先把亮和然串起来
liang().then(ran).then(dong)
//return
//new Promise
//同时
//执行liang的任务
//liang任务执行完
//调用door()开门 -> 自动执行.then()中的ran()
// ran() return new Promise
// .then(dong)
// 同时执行ran()的内容
// ran()执行完调用door()
// 开门!.then()后的dong()
// 才开始执行
异步任务间传参:
(1). 其实door()开门时,是可以顺便传参的: door(实参值)
(2). 下一项任务,在自己的形参列表中定义形参变量接住上一个任务door()传过来的实参值。
示例: 在前后两个异步任务之间传参
function liang(){//亮哥:异步
return new Promise(function(door){
var bang="接力棒";
console.log(`liang拿着 ${bang} 起跑...`);
setTimeout(function(){//异步
console.log(`liang拿着 ${bang} 到达终点!`);//亮最后执行的一句话
//之后,主动调用door开门!
// 发
door(bang);//自动执行.then()中串联的下一项任务
},6000)
})
}
// 接
function ran(bang2){//然哥:异步
//bang2=liang中door()给的"接力棒"
return new Promise(function(door){
console.log(`ran拿着 ${bang2} 起跑...`);
setTimeout(function(){//异步
console.log(`ran拿着 ${bang2} 到达终点!`);//然最后执行的一句话
//之后,主动调用door
// 发
door(bang2); //通知.then中串联的下一项任务可以开始执行!
},4000)
})
}
// 接
function dong(bang3){//东哥:异步
//bang3=ran()中door()传来的"接力棒"
console.log(`dong拿着 ${bang3} 起跑...`);
setTimeout(function(){//异步: 在主程序之外
console.log(`dong拿着 ${bang3} 到达终点!`)
},2000)
}
//先把亮和然串起来
liang().then(ran).then(dong)
坑: door()中只能接受一个变量!
(1). 如果传多个变量,也只有第一个变量的值,可以传到下一个任务!
(2). 如果硬要传多个值,可以放在数组或对象中传递:
door([值1,值2,…]) 或 door({属性1:值1, 属性2:值2,…})
参数解构 参数解构
function 下一项任务([形参1, 形参2]) 或 ({属性1, 属性2})
(3). 示例: 在前后两个异步任务之间传多个值:
function liang(){//亮哥:异步
return new Promise(function(door){
var bang="接力棒";
var qkl="巧克力";
console.log(`liang拿着 ${bang} 起跑...`);
setTimeout(function(){//异步
console.log(`liang拿着 ${bang} 到达终点!`);//亮最后执行的一句话
//之后,主动调用door开门!
// 发
// 0 1
//door([bang,qkl]);//自动执行.then()中串联的下一项任务
// 属性名:属性值 属性名:属性值
//door({bang:bang, qkl:qkl});
door({bang, qkl})
},6000)
})
}
// 接
// 0 1
//function ran([bang,qkl]){//然哥:异步
// 属性名:形参
// 配对 接值
//function ran({bang:bang, qkl:qkl}){
function ran({bang,qkl}){
//bang=liang中door({})中bang给的"接力棒"
//qkl=liang中door({})中qkl给的"巧克力"
return new Promise(function(door){
console.log(`ran吃了${qkl}`);
console.log(`ran拿着 ${bang} 起跑...`);
setTimeout(function(){//异步
console.log(`ran拿着 ${bang} 到达终点!`);//然最后执行的一句话
//之后,主动调用door
// 发
door(bang); //通知.then中串联的下一项任务可以开始执行!
},4000)
})
}
// 接
function dong(bang3){//东哥:异步
//bang3=ran()中door()传来的"接力棒"
console.log(`dong拿着 ${bang3} 起跑...`);
setTimeout(function(){//异步: 在主程序之外
console.log(`dong拿着 ${bang3} 到达终点!`)
},2000)
}
//先把亮和然串起来
liang().then(ran).then(dong)
错误处理:
(1). 其实: new Promise赠送了两个门:
a. door,通往正常路线下一个.then的门
b. err, 通往出错路线下一个.catch的门
c. 所以: new Promise(function(door, err){ … })
(2). 在异步任务中:
a. 如果正常执行,开door(实参值),接下来走.then()
b. 如果出错,开err(错误消息),接下来走最后一个.catch()
(3). 接住错误消息:
.then(…).then(…).catch(function(errMsg){ … })
示例: 模拟其中一个异步任务出错的情况:
function liang(){//亮哥:异步
// 赠两个门 正确 出错
return new Promise(function(door,err){
var bang="接力棒";
console.log(`liang拿着 ${bang} 起跑...`);
setTimeout(function(){//异步
//用随机数模拟出错的概率
var r=Math.random();//0~1
//假设r<0.6 60% 可以正常执行
//如果没有摔倒
if(r<0.6){
console.log(`liang拿着 ${bang} 到达终点!`);//亮最后执行的一句话
//之后,主动调用door开门!
door(bang);//自动执行.then()中串联的下一项任务
}else{//否则如果摔倒了r>0.4 40%
//而是走错误处理的门
err("liang摔倒了!退赛!")
}
},6000)
})
}
function ran(bang2){//然哥:异步
//bang2=liang中door()给的"接力棒"
return new Promise(function(door,err){
console.log(`ran拿着 ${bang2} 起跑...`);
setTimeout(function(){//异步'
//用随机数模拟出错的概率
var r=Math.random();//0~1
//假设r<0.6 60% 可以正常执行
//如果没有摔倒
if(r<0.6){
console.log(`ran拿着 ${bang2} 到达终点!`);//然最后执行的一句话
//之后,主动调用door
door(bang2); //通知.then中串联的下一项任务可以开始执行!
}else{//否则如果摔倒了r>0.4 40%
//而是走错误处理的门
err("ran摔倒了!退赛!")
}
},4000)
})
}
function dong(bang3){//东哥:异步
//bang3=ran()中door()传来的"接力棒"
console.log(`dong拿着 ${bang3} 起跑...`);
setTimeout(function(){//异步: 在主程序之外
console.log(`dong拿着 ${bang3} 到达终点!`)
},2000)
}
//先把亮和然串起来
liang().then(ran).then(dong).catch(function(errMsg){
console.error(errMsg);
})
Promise的三个状态(学名):
(1). 三个状态: pending(挂起/等待), fulfilled(成功), rejected(失败)
(2). 其实new Promise赠给我们的两个门也是有学名的:
new Promise(function(resolve, reject){ ... })
同意 拒绝
(3). 三个状态的切换:
a. 执行new Promise()时,异步任务执行完之前,promise对象处于pending状态
b. 当异步任务执行成功,我们调用resolve()开门时,整个promise对象就变成fulfilled状态,就自动调用.then()中的下一项任务
c. 当异步任务执行过程中出错,我们调用reject()开门时,整个promise对象就变成rejected状态,就不再调用.then(),而是转向调用结尾的.catch()
多个异步任务同时执行,但是只有在最后一个任务执行完才做一件事情。
(1). 错误: 顺序调用多个异步任务,在最后执行想要执行的代码。
(2). 因为: 主程序中的代码绝对不会等异步任务执行完才执行。一定是,多个异步任务刚开始执行时,主程序的代码就会立刻抢先执行!
(3). 正确: Promise.all([格子间1, 格子间2, 格子间3, …]).then(最后一项任务)
(4). 原理:
a. all后的数组中每个格子间的异步任务都是并发执行的!谁也不等谁!
b. 但是,all后的.then()注定会等最慢的一个格子间执行完,才自动执行!
(5). 示例: 等待多个异步任务并发执行,都结束后才执行一项任务:
function liang(){//亮哥:异步
return new Promise(function(resolve){
console.log(`liang起跑...`);
setTimeout(function(){//异步
console.log(`liang到达终点!`);//亮最后执行的一句话
//之后,主动调用resolve开门!
resolve();//自动执行.then()中串联的下一项任务
},6000)
})
}
function ran(){//然哥:异步
return new Promise(function(resolve){
console.log(`ran起跑...`);
setTimeout(function(){//异步
console.log(`ran到达终点!`);//然最后执行的一句话
//之后,主动调用resolve
resolve(); //通知.then中串联的下一项任务可以开始执行!
},4000)
})
}
function dong(){//东哥:异步
return new Promise(function(resolve){
console.log(`dong起跑...`);
setTimeout(function(){//异步: 在主程序之外
console.log(`dong到达终点!`);
resolve();
},2000)
})
}
//希望三个人百米赛跑(谁也不等谁),但是最后一个人跑完时,才输出比赛结束!
//错误:
// liang();//异步
// ran();//异步
// dong();//异步
// console.log(`比赛结束!`);
//正确:
Promise.all([
//只有调用异步函数,才能返回new Promise()格子间
liang(),//return new Promise()
//resolve()
ran(),//return new Promise()
//resolve()
dong()//return new Promise()
//resolve()
]).then(function(){ console.log("比赛结束!") })
Promise.all的返回值:
a. 接: Promise.all([…]).then(function(arr){ … })
b. arr中接到的返回值的顺序: 和all中异步函数的顺序一致,与执行结束的先后顺序无关!