promise详解
一、概念
- promise 是异步编程的一种解决方案,最典型的就是网络请求。
- 封装网络请求的函数,不能立即拿到结果,又不能等网络请求数据,只能传入另一个函数,让他去请求数据,当函数继续执行,当请求数据成功后在通过回调函数传回来。当检测到需要进行网络请求的时候,因为js是单线程,如果直接跑去进行网络请求,则用户界面将看不到任何的页面渲染和反应,即阻塞,所以每当有网络请求的时候,都会额外分出异步任务,既保证了网络请求,也保证了与用户的交互,最后当网络请求完成就把数据回调当前位置。
- promise在有异步操作时使用,使用promise对这个异步操作进行封装。
- promise结构清晰,逻辑清晰,不会陷入异步地狱。
- 把嵌套编程改为链式编程,每个异步函数都封装promise。
一、Promise
1.1 为什么要使用promise
- 主要为了解决回调地狱的问题
- 异步结构不清晰,promise可以让异步操作结构变得很清晰
1.2 promise语法
executor是带有resolve和reject两个参数的函数。Promise构造函数执行时立即调动exector函数,resolve和reject两个函数作为参数传递给executor(executor函数在Promise构造函数返回所建promise实例对象前被调用)。resolve和reject函数被调用时,分别将promise的状态改为fulfilled(完成)或reject(失败)。executor内部通常会执行一些异步操作,一旦异步操作执行完毕(可能成功/失败),要么调用resolve函数将promise状态更改成fulfilled,要么调用reject函数将promise的装填改为rejected。如果在executor函数中抛出一个错误,那么该promise状态为rejected。executor函数的返回值被忽略。
简单理解上面一段话,new Promise() 里面接收一个函数,这个函数会理解执行,函数里面有两个参数resolve和reject,函数的执行体里面是异步的操作,异步操作的返回值被忽略。
- 成功:状态pending->fulfilled,并且用resolve去接收成功的值
- 失败:状态pending->rejected,并且用reject去接收失败的值
- 3中状态两种结果
javascript
new Promise(function(resolve, reject){...})
简单的例子
javascript
let p1 = new Promise(function(resolve,reject){
setTimeout(function(){
let num = new Date().getTime()
num%2 == 0?resolve('成功!': resolve('失败!'))
},200)
})
p1.then(function(value){
console.log(value)
},function(reson){
console.log(reson)
})
连写
javascript
let p1 = new Promise(function(resolve,reject){
setTimeout(function(){
let num = new Date().getTime()
num%2 == 0 ? resolve('成功!') : resolve('失败!')
},200)
}).then(function(value){
console.log(value)
},function(reason){
console.log(reason)
})
如果一个promise即调用了resolve,又调用了reject,谁先调用,最后就走对应的方法
javascript
new Promise(function(resolve, reject){
resolve("成功!")
reject("失败!")
console.log('执行了!')
}).then(value => {
console.log(value)
}, reason => {
console.log(reason)
})
如果没有成功, 刚在then的第二个参数写的失败的回调函数,其实也可以用catch
javascript
new Promise(function(resolve, reject){
reject("失败!")
resolve("成功")
})
.then(value => {
console.log(value)
})
.catch(reason => {
console.log(reason)
})
1.3 Promise.resolve
成功的语法糖
javascript
let p1 = new Promise(function(resolve, reject){
resolve(11)
})
p1.then(function(value){
console.log(value)
})
Promise.resolve()
javascript
const p1 = Promise.resolve(11) // 跟上面是一样的
p1.then(value => {
console.log(value)
})
1.4 Promise.reject()
失败的语法糖
javascript
const p3 = Promise.reject(33)
p3.then(null,reason => {
console.log(reason)
})
1.5 Promise.all()
Promise.All():发送了多个请求,只有全部成功才走成功的处理,只要其中有一个失败就失败。
- 失败原因,走第一个失败的原因
ini
const p1 = Promise.resolve(11)
const p2 = Promise.reject(22)
const p3 = Promise.reject(33)
const pAll = Promise.all([p1, p2, p3])
pAll.then(values => {
console.log(values)
})
1.6 Promise.race()
多个异步任务,谁先执行完就用谁的,可以用setTimeout延迟去模拟。
ini
const p1 = Promise.resolve(11)
const p2 = Promise.resolve(22)
const p3 = Promise.resolve(33)
const pRace = Promise.race([p1, p2, p3])
pRace.then(value => {
console.log(value)
})
如果第一个执行完是一个失败的,那就走失败结果。
ini
const p1 = Promise.reject(11)
const p2 = Promise.resolve(22)
const p3 = Promise.resolve(33)
const pRace = Promise.race([p1, p2, p3])
pRace.then(value => {
console.log(value)
},reason => {
console.log(reason)
})
7、promise 理解
7.1 如何改变promise的状态?
scss
resolve(value): 如果当前是pendding 就会变为 resolved
reject(reason): 如果当前是pendding 就会变为rejected
抛出异常: 如果当前是pendding 就会变为rejected
javascript
// 如果当前是pendding就会变为rejected,内部抛出也是这样
const p = new Promise((resolve, reject) => {
throw new Error("出错了!")
})
const P = new Promise((resolve, reject) => {
// resolve(1) // promise 变为 resolve 成功状态
// reject(2) // promise 变为 rejected 失败状态
// throw new Error("出错了!") // 抛出异常 promise 变为 rejected 失败状态, reason 为抛出的 error
throw 3
})
p.then(reson => {
console.log("reason:", reason) // 3
})
7.2、一个promise制定多个成功/失败回调函数,都会调用吗?
arduino
// 都会调用
javascript
const p1 = Promise.resolve('11')
p1.then(value => {
console.log("第一次:" + value)
})
p1.then(value => {
console.log("第二次:" + value)
})
7.3、改变promise 状态和指定回调函数谁先谁后
-
都有可能,正常情况下时先指定回调函数再改变状态,但也可以先改变状态再指定回调函数
-
如何先改变状态再指定回调?
- 在执行器中直接调用 resolve() / reject()
- 延迟更长时间才调用then()
javascript
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1) // 后改变的状态(同时指定数据),异步执行回调函数
},200)
}).then(
// 先指定回调函数,保存当前指定的回调函数
value => {
console.log("value:", value) // value:1
}
)
7.4 、 promise.then() 返回的新的promise 的结果是由什么决定?
如果没有返回值也没有抛出错误,就走成功underfined
-
简单表达: 由then() 指定的回调函数执行的结果决定。
-
详细表达:
- 如果抛出异常,新promise变为 rejected, reason 为抛出的异常
- 如果返回的是非 promise 的任意值,新的 promise变为 resolved,value为返回的值
- 如果返回的是另一个新 promise ,此 promise 的结果就会成为新 promise的结果
javascript
new Promise((resolve, reject) => {
setTimeout(function() {
resolve(11)
},1000)
}).then(value => {
console.log("第一次:" + value)
}).then(value => {
console.log("第二次:" + value)
})
javascript
new Promise((resolve,reject) => {
setTimeout(function() {
reject(11)
},1000)
}).then(value => {
console.log("成功第一次:" + value)
},reason => {
console.log("失败第一次:" + reason)
}).then(value => {
console.log("成功第二次:" + value)
},reason => {
console.log("失败第二次:" + reason)
})
以下都是针对第二次then 的结果
javascript
new Promise((resolve, reject) => {
setTimeout(function() {
resolve(11)
},1000)
}).then(value => {
console.log("成功第一次:" + value)
// retuen 2 // 成功第二次
// return Promise.resolve(2) // 成功第二次2
// retuen Promise.reject(2) // 失败第二次2
throw 3 // 失败第二次
},reason => {
console.log("失败第一次" + reason)
}).then(value => {
console.log("成功第二次" + value)
},reason =>{
console.log("失败第二次" + reason)
})
7.5、 promise 如何串连多个操作任务
- promise 的 then() 返回一个新的 promise ,可以看成 then() 的链式调用。
- 通过then的链式调用串连多个同步/异步任务。
javascript
new Promise((resolve,reject) => {
setTimeout(() => {
console.log("执行异步任务1(异步)")
resolve(1)
},1000)
}).then(value => {
console.log("任务1的结果:" + value)
console.log("执行任务2(同步):")
return 2
}).then(value => {
console.log("任务2的结果:" + value)
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("执行任务3(异步)")
resolve(3)
},1000)
})
}).then(value => {
console.log("任务3的结果" + value)
})
7.6 promise 异常穿透
- 当使用promise的then链式调用时, 可以在最后指定失败的回调
- 前面任何操作出了异常,都会传到最后失败的回调中处理。
第一个走的失败回调,但是失败的回调没有写,默认会执行 reason => { throw reason },到第三个时, 由于第二个没有抛出异常,也没有返回值,所以走成功值为underfined
catch在后面也不会成功,因为第三个走的成功,所以不会执行catch
javascript
new Promise((resolve, reject) => {
reject(1)
}).then(value => {
console.log("onResolveed1():", value)
return Promise.reject(2)
}).then(value => {
console.log("onResolve2():", value)
return 3
}, reason => {
console.log("第二个失败!" + reason)
}).then(value => {
console.log("onResolveed3()", value)
}).catch(err => {
console.log("catch:" + err)
})
/*
第二个失败!1
onResolveed3() undefined
*/
默认在catch后面的then函数,执行成功和失败的回调和上面的规则是一样的
javascript
new Promise((resolve, reject) => {
reject(1)
}).catch(err => {
console.log(err)
}).then(value => {
console.log("成功" + value)
}, reason => {
console.log("失败" + reason)
})
/*
1
成功underfined
*/
如果不想执行后面then的函数呢?看下一个,中断promise链
7.7、中断promise链
- 当时用 promise的 then 的链式调用时,在中间中断, 不在调用后面的回调函数
- 办法: 在回调函数中返回一个pendding状态的promise对象
javascript
new Promise((resolve, reject) => {
reject(1)
}).catch(err => {
console.log(err)
return new Promise((resolve, reject) => {}) // 返回一个 penddin状态的 promise
}).then(value => {
console.log("成功" + value)
},reason => {
console.log("失败" + reason)
})
二、async 和 await
1. async
- 函数的返回值为promise 对象
- promise 对象的结果由 async 函数执行的返回值决定
只要增加了 async ,返回一个promise , 里面保存了状态, 如果async 函数成功, 下面就是走成功的回调,如果是失败,就失败的回调函数。
javascript
async function fn1() {
return 1
}
let result = fn1
console.log(result)
// Promise
javascript
async function fn1() {
return 1
}
fn1().then(value => {
console.log(value)
})
await
该指令会暂停异步函数的执行,并等待Promise执行, 然后继续执行异步函数,并返回结果。
- await 右侧的表达式一般为promise对象, 但也可以使其他的值
- 如果表达式是promise对象,await返回的是promise成功的值
- 如果表达式是其他值,直接将此值作为await的返回值
如果value的右边是promise,返回的是promise成功时候的值
如果value的右边是promise,返回的是promise失败时候的值,就用 try catch 来获取
如果value的右边不是promise,返回的是值本身
scss
function fn2() {
return new Promise((resolve,reject) => {
setTimeout(() => {
resolve(5)
},1000)
})
}
async function fn1() {
const value = awiat fn2()
console.log(value)
}
fn1() //5
注意:
await 必须写在async函数中,但是async函数中可以没有await
如果await的promise失败了,就会抛出异常,需要通过try...catch...来捕获处理
三、JS异步之宏队列与微队列
-
JS中用来存储执行回调函数的队列包含2个不同特定的队列
-
宏队列: 用来保存待执行的宏任务,比如:定时器回调,DOM事件回调,Ajax回调
-
微队列: 用来保存待执行的微任务,比如:promise的回调,MutationObserver的回调
-
JS执行时会区别这两个队列
- js引擎首先必须限制性所有的初始化同步任务代码
- 每次准备取出第一个宏任务钱,都要将所有的微任务一个一个取出来执行
注意: promise放在微任务里面,需要更改状态才会放到微任务里面,比如从pendding => resolved
或者 pendding变为 rejected 才会放到微队列
javascript
setTimeout(() => {
console.log("settimeout")
},0)
Promise.resolve(1).then(value => {
console.log("promise" + value)
})
/*
promise1
settimeout
*/
js会把程序走一遍,定时器加到宏队列,promise加到微队列
每次会先把微队列执行完再执行宏队列
javascript
setTimeout(() => {
console.log("定时器1")
},1000)
setTimeout(() => {
console.log("定时器2")
},1000)
Promise.resolve(1).then(value => {
console.log("第一个promise" + value)
})
Promise.resolve(2).then(value => {
console.log("第二个promise" + value)
})
/*
第一个promise1
第二个promise2
定时器1
定时器2
*/
看下面是先执行 第三个promise ,还是先执行定时器2
javascript
setTimeout(() => {
console.log("定时器1")
Promise.resolve(3).then(value => {
console.log("第三个promise" + value)
})
},1000)
setTimeout(() => {
console.log("定时器2")
},1000)
Promise.resolve(1).then(value => {
console.log("第一个promise" + value)
})
Promise.resolve(2).then(value => {
console.log("第二个promise" + value)
})
/*
第一个promise1
第二个promise2
定时器1
第三个promise3
定时器2
*/
当执行第一个定时器时,就把promise添加到了微队列
执行定时器2的时候,这个时候吧微队列的取出来执行,所以第三个promise先执行 定时器2后执行。