Promise
回调函数
运行某个函数实现某个功能的时候,传入一个函数作为参数,当发生某件事情的时候,会执行该函数,由于JS中的函数是一等公民,它和其他数据类型一样,可以作为参数传递也可以作为返回值返回,所以经常能够看到回调函数使用。
例如
<button id="btn1">btn1:给btn2注册事件</button>
<button id="btn2">btn2:给btn3注册事件</button>
<button id="btn3">btn3:点击以后会弹出一个helloworld</button>
<script>
// 点击btn3弹出一个hello world
const btn1 = document.getElementById("btn1");
const btn2 = document.getElementById("btn2");
const btn3 = document.getElementById("btn3");
btn1.addEventListener("click", function () {
btn2.addEventListener("click",function(){
btn3.addEventListener("click",function(){
alert("hello world")
})
})
})
</script>
回调地域
在异步实现中,回调函数的使用是不可避免的。JS的异步是单线程非阻塞式的。它将一个异步动作分为两步,第一步执行异步方法,然后代码接着往下执行。然后在后面的某个时刻调用第二步的回调函数,完成后续动作。
有的时候,我们希望在异步操作中加入同步的行为。比如,我想打印4句话,但是每句话都在前一句话的基础上延迟2秒输出。代码如下:
setTimeout(function(){
console.log("first");
setTimeout(function(){
console.log("second");
setTimeout(function(){
console.log("third");
setTimeout(function(){
console.log("fourth");
},2000);
},2000);
},2000);
},2000);
这段代码能够实现想要的功能,但是如果输出的内容越来越多,嵌套的代码也会增多。某个异步操作需要等待之前的异步操作完成,无论回调是事件还是其他函数,都会陷入不断的嵌套,这样就会越套越多,代码越来越来复杂,俗称“回调地狱”。
Promise
为了解决这个问题,在ES6中加入了一个新的对象Promise。
Promise并不是消除回调,只是让回调变得更简单,变更可控
new Promise(function(resolve,reject){
//dosomething
});
首先需要创建一个Promise对象,该对象的构造函数中接收一个回调函数,回调函数中可以接收两个参数,resolve和reject。注意,这个回调函数是在Promise创建后就会调用。它实际上就是异步操作的第一步。Promise把两个步骤分开了,第二步通过Promise对象的then方法实现。
let pm = new Promise(function(resolve,reject){
//dosomething
});
console.log("go on");
pm.then(function(){
console.log("异步完成");
});
注意 then方法的回调函数不是说只要then方法一调用它就会调用,而是在Promise的回调函数中通过调用resolve触发的。
let pm = new Promise(function(resolve,reject){
resolve();
});
console.log("go on");
pm.then(function(){
console.log("异步完成");
});
实际上Promise实现异步的原理和之前纯用回调函数的原理是一样的。只是Promise的做法是将两个步骤分开来写。then方法的回调函数同样会先放入队列中,等待所有的同步方法执行完后,同时Promise中的resolve也被调用后,该回调函数才会执行。
reject是出现错误时调用的方法。它触发的不是then中的回调函数,而是catch中的回调函数。比如:
let err = false;
let pm = new Promise(function(resolve,reject){
if(!err){
resolve("this is data");
}else{
reject("fail");
}
});
console.log("go on");
pm.then(function(data){
console.log("异步完成",data);
});
pm.catch(function(err){
console.log("出现错误",err);
});
时间函数的异步操作用Promise实现
let timeout = function(time){
return new Promise(function(resolve,reject){
setTimeout(function(){
resolve();
},time);
});
}
console.log("go on");
timeout(2000).then(function(){
console.log("first");
return timeout(2000);
}).then(function(){
console.log("second");
return timeout(2000);
}).then(function(){
console.log("third");
return timeout(2000);
}).then(function(){
console.log("fourth");
return timeout(2000);
});
由于需要多次创建Promise对象,所以用了timeout函数将它封装起来,每次调用它都会返回一个新的Promise对象。当then方法调用后,其内部的回调函数默认会将当前的Promise对象返回。后面继续用then方法来触发异步完成的回调函数。这样就可以做到同步的效果,从而避免了过多的回调嵌套带来的“回调地狱”问题。
参考:https://zhuanlan.zhihu.com/p/74637286
finally()
ES2018出来的,没有参数的,当promise为已决时运行该函数
Promise.all()
方法用于将多个 Promise 实例 这个方法会返回一个新的promise对象,如果里面所有的promise对象都成功才会触发,一旦有一个失败,则该promise对象为失败
const proms = [];
for(let i = 0;i < 10;i ++){
proms.push(new Promise((resolve,reject) => {
// resolve(1)
setTimeout( ()=> {
if(Math.random() < 0.9){
console.log(i,"完成")
resolve(i)
}else{
console.log(i,"失败")
reject(i)
}
},1000)
}))
}
// 所有的都成功才为成功,只要有一个失败,就失败
const pro = Promise.all(proms);
pro.then(data => {
console.log('全部完成',data);
})
pro.catch(err => {
console.log('有失败的',err)
})
Promise.race()
方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。当参数中的任意一个promise对象完成时候,就马上回去使用完成的这个promise对象的结果,不管这个结果成功还是失败
function getRandom(min,max){
return Math.floor(Math.random() * (max - min) + min)
}
const proms = [];
for(let i = 0;i < 10;i ++){
proms.push(new Promise((resolve,reject) => {
// resolve(1)
setTimeout( ()=> {
if(Math.random() < 0.9){
console.log(i,"完成")
resolve(i)
}else{
console.log(i,"失败")
reject(i)
}
},getRandom(1000,5000))
}))
}
const pro = Promise.race(proms);
pro.then(data => {
console.log('有人完成了',data);
})
pro.catch(err => {
console.log('有失败的',err)
})