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

相关推荐
大圣编程1 小时前
Python中continue语句的用法是什么?
开发语言·前端·python
yuhaiqiang1 小时前
随手 vibecoding 的浏览器插件已经 6000 多次下载,聊聊他的产品设计
前端·后端·面试
之歆2 小时前
Vue商品详情与放大镜组件
前端·javascript·vue.js
再吃一根胡萝卜2 小时前
如何把小米 MiMo 接入 CodeBuddy,打造私有 Agent
前端
负责的蛋挞3 小时前
异步HttpModule的实现方式
java·服务器·前端
丹宇码农6 小时前
把 HLS 字幕玩出花:zwPlayer 如何让 M3U8 视频支持全文搜索、翻译与码率自适应
前端·javascript·音视频·hls·视频播放器
2501_943782356 小时前
【共创季稿事节】猜数字游戏:二分法思维与交互式反馈
前端·游戏·microsoft·harmonyos·鸿蒙·鸿蒙系统
GV191rLvq6 小时前
基于Socket实现的最简单的Web服务器【ASP.NET原理分析】
服务器·前端·asp.net
吠品6 小时前
LangChain 里 tool_call_id 为空?一次 MCP 工具集成的排查记录
前端