promise详解

promise详解

一、概念

  1. promise 是异步编程的一种解决方案,最典型的就是网络请求。
  2. 封装网络请求的函数,不能立即拿到结果,又不能等网络请求数据,只能传入另一个函数,让他去请求数据,当函数继续执行,当请求数据成功后在通过回调函数传回来。当检测到需要进行网络请求的时候,因为js是单线程,如果直接跑去进行网络请求,则用户界面将看不到任何的页面渲染和反应,即阻塞,所以每当有网络请求的时候,都会额外分出异步任务,既保证了网络请求,也保证了与用户的交互,最后当网络请求完成就把数据回调当前位置。
  3. promise在有异步操作时使用,使用promise对这个异步操作进行封装。
  4. promise结构清晰,逻辑清晰,不会陷入异步地狱。
  5. 把嵌套编程改为链式编程,每个异步函数都封装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 状态和指定回调函数谁先谁后

  1. 都有可能,正常情况下时先指定回调函数再改变状态,但也可以先改变状态再指定回调函数

  2. 如何先改变状态再指定回调?

    • 在执行器中直接调用 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

  1. 简单表达: 由then() 指定的回调函数执行的结果决定。

  2. 详细表达:

    • 如果抛出异常,新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 如何串连多个操作任务

  1. promise 的 then() 返回一个新的 promise ,可以看成 then() 的链式调用。
  2. 通过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 异常穿透

  1. 当使用promise的then链式调用时, 可以在最后指定失败的回调
  2. 前面任何操作出了异常,都会传到最后失败的回调中处理。

第一个走的失败回调,但是失败的回调没有写,默认会执行 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链

  1. 当时用 promise的 then 的链式调用时,在中间中断, 不在调用后面的回调函数
  2. 办法: 在回调函数中返回一个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

  1. 函数的返回值为promise 对象
  2. 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执行, 然后继续执行异步函数,并返回结果。

  1. await 右侧的表达式一般为promise对象, 但也可以使其他的值
  2. 如果表达式是promise对象,await返回的是promise成功的值
  3. 如果表达式是其他值,直接将此值作为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异步之宏队列与微队列

  1. JS中用来存储执行回调函数的队列包含2个不同特定的队列

  2. 宏队列: 用来保存待执行的宏任务,比如:定时器回调,DOM事件回调,Ajax回调

  3. 微队列: 用来保存待执行的微任务,比如:promise的回调,MutationObserver的回调

  4. 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后执行。

相关推荐
LuciferHuang4 小时前
震惊!三万star开源项目竟有致命Bug?
前端·javascript·debug
GISer_Jing4 小时前
前端实习总结——案例与大纲
前端·javascript
天天进步20154 小时前
前端工程化:Webpack从入门到精通
前端·webpack·node.js
姑苏洛言5 小时前
编写产品需求文档:黄历日历小程序
前端·javascript·后端
知识分享小能手5 小时前
Vue3 学习教程,从入门到精通,使用 VSCode 开发 Vue3 的详细指南(3)
前端·javascript·vue.js·学习·前端框架·vue·vue3
姑苏洛言5 小时前
搭建一款结合传统黄历功能的日历小程序
前端·javascript·后端
hackchen6 小时前
Go与JS无缝协作:Goja引擎实战之错误处理最佳实践
开发语言·javascript·golang
你的人类朋友6 小时前
🤔什么时候用BFF架构?
前端·javascript·后端
知识分享小能手7 小时前
Bootstrap 5学习教程,从入门到精通,Bootstrap 5 表单验证语法知识点及案例代码(34)
前端·javascript·学习·typescript·bootstrap·html·css3