这是我参与 8 月更文挑战的第 5 天,活动详情查看: 8月更文挑战
Promise
- 在ES2015(ES6)中标准化的功能,中文名(暂定)“期约”,用于给异步编程提供一种书写更加合理,功能更强的统一的解决方案。
同步与异步
- JavaScript中存在同步与异步的概念。
- 常见的异步任务有:事件、定时器、Ajax
- 同步任务总是顺序执行,而异步任务执行结果与书写顺序无关
- 例如:定时器结束时间取决于延迟时间;Ajax的响应成功时间取决于数据量和网速
- 异步任务需要通过回调函数进行结果处理。
总结
- JavaScript存在同步与异步的概念
- 同步代码顺序执行
- 异步代码与顺序无关,需要通过回调函数进行处理
Promise简介
- 问题:如果存在多个具有依赖关系的异步任务,该如何处理?
- 解决代码如下图,此时出现了“回调地狱”的问题
Prmoise基础
- Promise是一个ES6提供的类,使用时需要创建一个实例对象,指代我们需要进行的一个(异步)操作。
- 创建完毕,这时promise处于一个“待定状态”,最终结果可能为“成功状态”或“失败状态”
- 特点:结果明确后无法更改。
- 回到操作:那么对应操作中,利于一个Ajax请求,当请求成功或失败时,可以通过相应的“状态”进行执行结果的判断,从而进行相应的操作
- 小结:Promise对象的操作就是通过状态变化上来触发对应的回调函数。
Promise使用方式
- 基础如图所示
- 步骤1:创建实例用于存储异步操作
- 步骤2:参数为函数,在创建实例时同步执行
- 步骤3:函数有两个参数,都是函数,调用时可决定Promise对象的状态
- resolve(data) 传递成功时的结果
- reject(err) 传递失败时的错误信息
- 注意:由于Promise对象的结果明确后无法更改,所以两个函数在逻辑中只能有一个被执行。
const promise = new Promise(function (){
// 传递成功,返回100
resolve(100)
})
- 步骤4:当Promise对象的状态确定时,会触发对应的回调函数进行处理,需要通过Promise对象的方法指定这些回调函数
- then() 成功时的处理函数
- catch() 失败时的处理函数
- 代码演示
// 创建Promise实例
const promise = new Promise(function (){
resolve(100)
})
// 回调函数处理
promise.then(function (value){
console.log("成功了,数据为:",value)
}).catch (function (err){
console.log("失败了,数据为:",err)
}
Promise练习:封装Ajax函数
-
回顾原生Ajax,请看这篇文章Ajax基础和手写封装Ajax函数
- 原生发送Ajax步骤
- 1.创建XMLHttpRequest类型的对象
- 2.准备发送,打开与一个网址之间的连接
- 3.执行发送动作
- 4.指定xhr状态变化事件处理函数
- 具体实现代码
function ajax (url) {
return new Promise(function (resolve,reject) {
// 创建XMLHttpRequest类型的对象
const xhr = new XMLHttpRequest()
// 准备发送,打开与一个网址之间的连接
xhr.open('GET',url)
xhr.responseType = 'json'
xhr.onload = function () {
if(this.status === 200){
// 接收响应数据,标记状态为成功,并传递给then中的回调处理
resolve(this.response)
}else {
// 失败时更改状态,并传递错误信息
reject(new Error(this.statusText))
}
}
// 执行发送动作
xhr.send()
})
}
ajax(successUrl).then(function (res) {
console.log(res)
}).catch(function (error) {
console.log(error)
})
- 如果仅仅是这样使用,请求多了,依然会出现嵌套的问题,这时要注意使用方式,不要嵌套书写,而是利用Promise的链式调用进行操作
// 错误写法
ajax(url).then(function (res) {
console.log(res)
ajax(url).then(function (res) {
console.log(res)
ajax(url).then(function (res) {
console.log(res)
})
})
})
链式调用
- then()调用的返回值为一个新的Promise对象,如果要在一次异步操作后再执行下一个异步操作,应当通过这个新的Promise对象进行处理,这样就可以避免回调地狱
- 正确写法
// 正确写法
ajax(url)
.then(function (res){
console.log(1)
})
.then(function (res){
console.log(2)
})
.then(function (res){
console.log(3)
})
- then()调用的返回值为一个新的Promise对象,如果要在一次异步操作后再执行下一个异步操作,应当通过这个新的Promise对象进行处理,这样就可以避免回调地狱
- 正确写法
// 正确写法
ajax(url)
.then(function (res){
console.log(1)
})
.then(function (res){
console.log(2)
})
.then(function (res){
console.log(3)
})
- 在then()中通过return返回Promise对象会被设置为then()的返回值
ajax(url1)
.then(function (res) {
console.log(res,1)
// 返回的promise对象,设置给下一个then()
return ajax(url2)
}).then(function (res) {
console.log(res,2)
return ajax(url3)
}).then(function (res) {
console.log(res,3)
return ajax(url4)
})
- 如果返回的是普通值,则会包裹在默认的Promise对象中,传递给then方法,不传时默认为undefined
ajax(url1)
.then(function (res) {
console.log(res,1)
// 返回的promise对象,设置给下一个then()
return ajax(url2)
}).then(function (res) {
console.log(res,2)
// 返回:普通值
return [1,2,3]
}).then(function (res) {
console.log(res,3) // 输出:[1,2,3],3
return ajax(url4)
})
总结
- Promise本质上就是通过回调函数定义异步任务结束后所需要执行的任务,只不过回调是通过then()进行设置,由于then()可以进行链式调用,所以避免了层层嵌套的书写方式。
Async函数
- Promise的出现解决了异步操作回调地狱的问题,但书写方式中函数出现了大量的回调,虽然没有嵌套,但可读性远不及同步代码清晰
- 为了让异步代码书写更加简洁、可读性更强,在ES2017中提出了Async函数(异步函数),用于简化Promise的书写方式,让代码格式更加接近同步代码写法
- Async是异步编程的新标准,是一种用于改进异步书写的语法糖,本质还是Promise对象
- 使用方式:
- 在任意函数前设置async关键字,将函数设置为async函数(异步函数)
- 在async函数内调用返回了promise对象的函数,并在调用前设置await
- 此时,调用的返回值会被await设置为resolve传递的数据,即成功的结果
- 失败时报错(需通过try...catch处理)
- 注意:await只能出现在async函数中
- 演示代码
async function fn () {
const data1 = await ajax(url)
console.log(1,data1)
const data2 = await ajax(url)
console.log(2,data2)
}
fn()
Try...catch
- 用于进行异常捕获(报错处理),当程序代码可能出现报错时,可以通过try...catch进行异常捕获
- 为了程序的健壮性,应当利用try...catch对async/await进行处理
- 演示代码
function ajax (url) {
return new Promise(function (resolve, reject) {
const xhr = new XMLHttpRequest()
xhr.open('GET', url)
xhr.responseType = 'json'
xhr.onload = function () {
if (this.status === 200) {
resolve(this.response)
} else {
reject(new Error(this.statusText))
}
}
xhr.send()
})
}
async function fn () {
const data1 = await ajax(successUrl)
console.log(1, data1)
const data2 = await ajax(successUrl)
console.log(2, data2)
// 例如:假设为避免请求3失败导致后续代码⽆法执⾏,可通过 try...catch 处理
try {
const data3 = await ajax(failUrl)
console.log(3, data3)
} catch (error) {
console.log('进⾏补救处理')
}
// 即使请求3失败,后续请求也可以继续执⾏。
const data4 = await ajax(successUrl)
console.log(4, data4)
}
fn()
扩充
- Promise可以解决“回调地狱”问题。
- 面试题:什么是Promise?
- 异步操作有一个“回调地狱”的问题
- 出现的时候:存在多个异步调用并且异步调用之间有依赖关系时,就会形成“回调地狱”的情况
- 为了解决这个问题,在ECMAScript2015(ES6)提出了Promise语法,专门来做异步的操作。
- 希望通过Promise将异步调用的功能写的更加优雅。
- async和await
- 异步处理,同步写法
-
推荐大家看看大佬总结的async和await
不可预知的try catch
- 允许对程序中出错的,进行补救。