Promise出现之前的异步编程
首先 什么是同步,什么是异步?
- 同步: 代码从上往下执行 遇见没有延迟的操作 就会一直等 等到完成 才会继续走
- 异步: 代码从上往下执行 遇见有延迟的操作(定时器,ajax,事件处理,读取文件等) 就执行后面的代码了 ,延迟的代码后面执行
那么,现在提一个要求,使用ajax发出请求获取数据,将得到的数据进行ajax请求,再将第二次获取到的数据进行网络请求…一直这样下去的话,就会形成以下情况:
$.ajax({
url:"./php/ok.php",
success:function(res){
console.log('ajax结果111',res)
$.ajax({
url:"./php/ok.php",
success:function(res){
console.log('ajax结果2222',res)
$.ajax({
url:"./php/ok.php",
success:function(res){
console.log('ajax结果2222',res)
$.ajax({
url:"./php/ok.php",
success:function(res){
console.log('ajax结果2222',res)
}
})
}
})
}
})
}
})
一直嵌套下去的ajax请求,最后会形成回调地狱,直到promise的出现,才解决了这个恶心的嵌套。
Promise
是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理且更强大。它最早由社区提出并实现,ES6将其写进了语言标准,统一了用法,并原生提供了Promise对象。
在一个页面中进行一个操作时,需要进行网络请求,如果这个网络请求是同步的,又因为网络请求需要时间,js代码就会停留在那一部分,就会使得这个页面进入阻塞状态,一直等到网络请求完之前都不能跟页面互动。
一般会给网络请求开启一个异步任务,让页面其他部分正常执行,网络请求完了以后会给一个回调函数,就可以从回调函数里得到网络请求得到的数据(或者请求失败的信息),给后面的js代码使用。
Promise的基本使用
Promise 使用时会传入一个函数作为参数,而这个函数要求传两个参数resolve和reject(resolve和reject也是函数),将需要异步操作的代码放入这个参数函数中,将异步请求得出的数据作为参数传入resolve函数中,在Promise后面使用.then(),其中参数为data,在then函数中单独执行异步请求的数据;Promise的另一个参数reject则是请求异步数据失败时调用,对应的函数.catch();再或者可以将then函数传入两个函数参数,第一个是成功的,第二个是失败的
//使用setTimeout()来模拟网络请求
//Hello作为参数,参数名为data,传入then函数中。
//成功时调用resolve,失败时调用reject
new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('Hell0')
reject('error message')
},1000)
}).then(data =>{
console.log(data);
}).catch(error =>{
console.log(error);
})
// 1s后输出:Hello
回调地狱的解决方法——Promise的链式调用
注:以下都用setTimeout() 来模拟网路请求
下面这段代码是模拟第一次网络请求得到数据h=100,再用h进行第二次网络请求,第二次网络请求会将h*10,然后输出这个结果,且每次请求都耗时1s,也就是在2s后输出1000
new Promise((resolve,reject) => {
setTimeout(() => {
let h = 100
resolve(h)
reject('error')
},1000)
}).then((result) => {
console.log('=======after 1s======')
return new Promise((resolve,reject) => {
setTimeout(() => {
let h2 = result * 10
resolve(h2)
},1000)
})
}).then((result) => {
console.log('=======after 2s======')
let h3 = result
console.log(h3);
})
咋一看这个链式调用很复杂,拆开来看就简单多了
new Promise((resolve,reject) => {
setTimeout(() => {
let h = 100
resolve(h)
reject('error')
},1000)
}).then((result) => {
console.log('=======after 1s======')
console.log(result)
})
首先,将return new Promise()后面的内容删去,就是最简单的Promise使用,将网络请求成功的数据h在then()得到
then((result) => {
console.log('=======after 1s======')
return new Promise((resolve,reject) => {
setTimeout(() => {
let h2 = result * 10
resolve(h2)
},1000)
})
}) //这一部分就是一个Promise()
在then()函数中return new Promise(),意思是,then()的调用就是在调用这个Promise,而在这个Promise中将h数据(也就是result)*10,的结果作为新的网络请求得到的数据。
.then((result) => {
console.log('=======after 1s======')
return new Promise((resolve,reject) => {
setTimeout(() => {
let h2 = result * 10
resolve(h2)
},1000)
})
}).then((result) => {
console.log('=======after 2s======')
let h3 = result
console.log(h3);
})
然后将这一个then(),也就是新的Promise使用then(),将数据取出,输出
Promise.all
多个网络请求同时发出请求,并要求同时得到时使用。
使用规则:Promise.all 参数是数组,数组元素是一个Promise函数 ,后面的then函数的参数就是一个数组,其中的元素就是每一个Promise返回的结果
// Promise.all 参数是数组,数组元素是一个Promise函数 ,后面的then函数的参数就是一个数组,其中的元素就是每一个Promise返回的结果
// 用于解决需要得到多个网络请求而请求时间有差异的情况,Promise.all能将多个网络请求的数据存放在一个数组里
/// 必须数组里面所有的promise执行完毕才成功。
Promise.all([
new Promise((resolve,rejevt) => {
setTimeout(() => {
resolve({name:'h',age:21})
},2000)
}),
new Promise((resolve,reject) => {
setTimeout(() => {
resolve({name:'ass',age:18})
reject('ass failed')
},1000)
})
]).then(results => {
console.log(results[0]);
console.log(results[1]);
})
可以看出,即使后定时2s后的结果放在前面,由于所有网络请求结果是放在一个数组中,并且要在所有数据成功得到后才进行统一处理。所以输出顺序是如上图所示。
async/await
async:
就是Generator函数的语法糖,它建立在Promises上,并且与所有现有的基于Promise的API兼容。
- 使用async声明一个异步函数 async function name(){}
- 自动将常规函数转化为Promise,返回值也是一个Promise对象
- 只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数
await
暂停异步的功能执行。
- 放置在Promise调用之前,await强制其他代码等待,直到Promise完成并返回结果
- 只能与Promise一起使用,不适用于回调
- 只能在async函数内部使用。
示例
首先封装一个返回Promise的网络请求函数
function axios(){
let p1=new Promise(function(resolve,reject) {
$.ajax({
url:"./php/ok.php",
success:function(res){
// console.log('ajax结果',res)
// console.log(2)
// 成功 调用resolve
resolve(res)
}
})
})
return p1
// axios返回一个 promise
}
// axios().then(function(res){
// console.log('结果',res)
// })
// 点击 发送ajax
// async await 最简单的使用 就是 可以省略掉then 简单快捷
// 代码看起来清晰
document.getElementById("btn").onclick= async ()=>{
// axios().then(function(res){
// console.log('结果',res)
// })
let res=await axios();// 这里会等待成功 才执行下面
let res=await axios();// 这里会等待成功 才执行下面
console.log('结果',res)
}
async和await 相比于Promise的优势
- 代码读起来更加同步,Promise虽然摆脱了回调地狱,但是then的链式调用也会带来额外的阅读负担
- Promise传递中间值非常麻烦,而async/await几乎是同步的写法,非常优雅
- 错误处理友好,async/await可以使用成熟的try/catch,Promise的错误捕获非常冗余
- 调试友好,Promise的调试很差,由于没有代码块,你不能在一个返回表达式的箭头函数中设置断点,如果你在一个.then代码块中使用调试器的步进功能,调试器并不会进入后续的.then代码块,因为调试器只能跟踪同步代码的每一步