初识Promise
- 为什么需要promise?
问题: 异步必须通过回调函数来返回结果,回调函数一多就会出现"回调地狱"问题。
- Promise
- Promise 可以帮助我们解决异步中的回调函数的问题。
- Promise 就是一个用来存储数据的容器。
它拥有着一套特殊的存取数据的方式,这个方式使得它里边可以存储异步调用的结果
在调试控制台中输出对象,[[]]
表示这个属性是私有属性,无法访问
[[Prototype]]: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: 1
创建对象
-
创建 Promise 时,构造函数中需要一个函数作为参数
-
Promise 构造函数的回调函数,它会在创建 Promise 时调用,调用时会有两个参数传递 进去
-
resolve 和 reject 是两个函数,通过这两个函数可以向 Promise 中存储数据
- resolve 在执行正常时存储数据
- reject 在执行错误时存储数据
-
通过函数来向 Promise 中添加数据,好处就是可以用来添加异步调用的数据
js
const promise=new Promise((resolve,reject)=>{
resolve("通过resolve存储数据")
//reject("通过reject存储数据")
})
console.log(promise)
// 大概的底层的实现
/*class Promise{
function resolve(data){
this.#PromiseResult=data
}
function reject(data){
this.#PromiseResult=data
}
constructor(cb){
cb(resolve,reject)
}
}*/
私有属性
PromiseResult
用来存储数据PromiseState
记录Promise的状态
实例方法
- then
- 可以通过 Promise 的实例方法 then 来读取 Promise 中存储的数据。
- then 需要两个回调函数作为参数,回调函数用来获取 Promise 中的数据。
- 通过 resolve 存储的数据,会调用第一个函数返回,可以在第一个函数中编写处理数据的代码。
- 通过 reject 存储的数据或者出现异常时,会调用第二个函数返回,可以在第二个函数中编写处理异常的代码。
js
const promise=new Promise((resolve,reject)=>{
resolve("通过resolve存储数据")
//reject("通过reject存储数据")
})
promise.then((result)=>{
console.log(1,result)
},(reason)=>{
console.log(2,reason)
})
//大概的底层实现
/*class Promise{
then(f1,f2){
if(this.#PromiseState===fulfilled){
f1(this.#PromiseResult)
}else if(this.#PromiseState===reject){
f2(this.#PromiseResult)
}
}
}*/
-
catch
- catch() 用法和 then 类似,但是只需要一个回调函数作为参数。
- catch()中的回调函数只会在 Promise 被拒绝时才调用。
- catch() 相当于 then(null, reason => {})。
- catch() 就是一个专门处理 Promise 异常的方法。
- 当Promise出现异常时,而整个调用链中没有出现catch,则异常会向外抛出(调用链前面的异常会抛给后面的处理,所以我们一般把catch放到最后)。
-
finally
- 无论是正常存储数据还是出现异常了,finally 总会执行。
- 但是 finally 的回调函数中不会接收到数据。
- finally()通常用来编写一些无论成功与否都要执行代码。
状态
Promise对象有三种状态:
- pending(进行中)、fulfilled(已成功)、rejected(已失败)。
- 一个promise对象只能改变一次状态,在PromiseResult中也只能赋一次值。
原理
- 当 Promise 创建时,PromiseState初始值为 pending。
- 当通过resolve存储数据时, PromiseState 变为fulfilled(完成) PromiseResult变为存储的数据。
- 当通过reject存储数据或出错时 PromiseState 变为rejected(拒绝,出错了)PromiseResult变为存储的数据或异常对象。
- 当我们通过then读取数据时,相当于为Promise设置了回调函数。
- 如果PromiseState变为fulfilled,则调用then的第一个回调函数来返回数据。
- 如果PromiseState变为rejected,则调用then的第二个回调函数来返回数据。
链式调用
text
promise中的
then (return new Promise())
catch
finally
- finally的返回值,不会存储到新的Promise中
这三个方法都会返回一个新的Promise,Promise中会存储回调函数的返回值
后边的方法(then和catch)读取的上一步的执行结果
如果上一步的执行结果不是当前想要的结果,则跳过当前的方法
- then()方法的返回值为一个Promise对象。
- then中不论调用完成的回调还是失败的回调,都会返回完成的Promise对象,并将回调的返回值封装到该Promise对象的PromiseResult,除非回调函数中抛出异常。
- 若方法中的回调函数的返回值是Promise对象,则then方法的返回值为该Promise对象。
- 若不是Promise对象,则将返回值封装到返回的Promise对象中的PromiseResult中。
js
const p = new Promise((resolve, reject) => {
resolve("数据1")
})
const p1 = p.then((result) => {
console.log(result)
return "数据2"
}, null)
const p2 = p1.then((result) => {
console.log(result)
}, null)
p2.then((result) => {
console.log(result)
})
/*
数据1
数据2
undefined*/
静态方法
创建即时对象:
- Promise.resolve() 创建一个立即完成的Promise
- Promise.reject() 创建一个立即拒绝的Promise
js
const p = Promise.resolve(10)//相当于创建对象
console.log(p)
//等价于
// new Promise((resolve, reject) => {
// resolve(10)
// })
all
Promise.all([...])
同时返回多个Promise的执行结果(执行结果的数组)其中有一个报错,就返回错误。Promise.allSettled([...])
同时返回多个Promise的执行结果(无论成功或失败) 成功:{status: 'fulfilled', value: 579} 失败:{status: 'rejected', reason: '哈哈'}。
js
Promise.all([
sum(123, 123),
sum(2, 555),
sum(43, 56)
]).then(r => {
console.log(r)
})//[246, 557, 99]
scss
Promise.allSettled([
sum(123, 123),
sum(2, 555),
Promise.reject("哈哈"),
sum(43, 56)
]).then(r => {
console.log(r)
})
最快
Promise.race([...])
返回执行最快的Promise(不考虑对错)。Promise.any([...])
返回执行最快的完成的Promise(考虑对错)。
如果any的参数数组中全为拒绝的Promise对象,则只能报错。
js
Promise.race([
Promise.reject(1111),
sum(123, 456),
sum(5, 6),
sum(33, 44)
]).then(r => {
console.log(r)
}).catch(r => {
console.log("错误")
})//错误
js
Promise.any([
Promise.reject(1111),
sum(123, 456),
sum(5, 6),
sum(33, 44)
]).then(r => {
console.log(r)
}).catch(r => {
console.log("错误")
})//579
事件机制
- JS是单线程的,它的运行时基于事件循环机制(event loop)。
- 调用栈放的是要执行的代码。
- 任务队列的是将要执行的代码。
- 当调用栈中的代码执行完毕后,队列中的代码才会按照顺序依次进入到栈中执行。
- 在JS中任务队列有两种。
- 宏任务队列 (大部分代码都去宏任务队列中去排队),如:定时器、dom绑定事件、ajax。
- 微任务队列 (Promise的回调函数(then、catch、finally))。
- 整个流程
① 执行调用栈中的代码 --> ② 执行微任务队列中的所有任务 --> ③ 执行宏任务队列中的所有任务
Promise的执行原理
- Promise在执行,then就相当于给Promise绑定了回调函数。
- 当Promise的状态从pending 变为 fulfilled/rejected时,then的回调函数会被放入到任务队列中。
queueMicrotask
js
// queueMicrotask(回调函数) 用来向微任务队列中添加一个任务
setTimeout(() => {
console.log(1)
}, 0) //宏任务
queueMicrotask(() => console.log(2)) // 微任务,队列中先进先出,执行按顺序
Promise.resolve().then(() => console.log(3)) // 微任务
console.log(4) // 同步栈
// 4 2 3 1
是否能看出以下程序输出顺序?
js
console.log(1);
setTimeout(() => console.log(2));
Promise.resolve().then(() => console.log(3));
Promise.resolve().then(() => setTimeout(() => console.log(4)));
//当它执行时才会把4放进宏任务队列
Promise.resolve().then(() => console.log(5));
setTimeout(() => console.log(6));
console.log(7);
//1735264
手写Promise
js
//起步构建
// 1.用类创建Promise,类中需要有个执行器executor
// 2.执行者中发生错误,交给异常状态处理
// 3.执行者中状态只能触发一次,状态触发一次之后,不能修改状态
// 4.执行者中的this,由调用执行者的作用域决定,因此我们需要将执行者中的this绑定为我们创建的Promise对象。
// 5.在构造函数中需要为Promise对象创建status和value记录Promise的状态和传值。
class MyPromise {
static PENDING = 'pending'
static FULFILLED = 'fulfilled'
static REJECTED = 'rejected'
constructor(executor) {
this.status = MyPromise.PENDING;
this.value = null;
this.callbacks = [];
try {
executor(this.resolve.bind(this), this.reject.bind(this))
} catch (error) {
this.reject(error)
}
}
resolve(value) {
if (this.status == MyPromise.PENDING) {
this.status = MyPromise.FULFILLED;
this.value = value
setTimeout(() => {
this.callbacks.map(item => {
item.onFulfilled(this.value);
})
})
}
}
reject(reason) {
if (this.status == MyPromise.PENDING) {
this.status = MyPromise.REJECTED;
this.value = reason
setTimeout(() => {
this.callbacks.map(item => {
item.onRejected(this.value);
})
})
}
}
//开始写then方法
//1.then接收2个参数,一个成功回调函数,一个失败回调函数
//2.then中发生错误,状态为rejected,交给下一个then处理
//3.then返回的也是一个Promise
//4.then的参数值可以为空,可以进行传值穿透
//5.then中的方法是异步执行的
//6.then需要等promise的状态改变后,才执行,并且异步执行
//7.then是可以链式操作的
//8.then的onFulfilled可以用来返回Promise对象,并且then的状态将以这个Promise为准
//9.then的默认状态是成功的,上一个Promise对象的状态不会影响下一个then的状态
//10.then返回的promise对象不是then相同的promise
then(onFulfilled, onRejected) {
if (typeof onFulfilled != 'function') {
onFulfilled = value => value
}
if (typeof onRejected != 'function') {
onRejected = reason => reason
}
let promise = new MyPromise((resolve, reject) => {
if (this.status == MyPromise.FULFILLED) {
setTimeout(() => {
this.parse(promise, onFulfilled(this.value), resolve, reject)
});
}
if (this.status == MyPromise.REJECTED) {
setTimeout(() => {
this.parse(promise, onRejected(this.value), resolve, reject)
})
}
if (this.status == MyPromise.PENDING) {
this.callbacks.push({
onFulfilled: value => {
this.parse(promise, onFulfilled(value), resolve, reject)
},
onRejected: reason => {
this.parse(promise, onRejected(reason), resolve, reject)
}
});
}
})
return promise
}
//整理冗余代码
parse(promise, result, resolve, reject) {
if (promise == result) {
throw new TypeError('Chaining cycle detected for promise')
}
try {
if (result instanceof MyPromise) {
result.then(resolve, reject)
} else {
resolve(result)
}
} catch (error) {
reject(error)
}
}
//Promise的静态方法,resolve
static resolve(value) {
return new MyPromise((resolve, reject) => {
if (value instanceof MyPromise) {
value.then(resolve, reject)
} else {
resolve(value)
}
})
}
//Promise的静态方法,reject
static reject(reason) {
return new MyPromise((resolve, reject) => {
reject(reason)
})
}
//Promise的静态方法,all
static all(promises) {
let values = [];
return new MyPromise((resolve, reject) => {
promises.forEach(promise => {
if (promise.status == MyPromise.FULFILLED) {
values.push(promise.value)
} else if (promise.status == MyPromise.REJECTED) {
reject(promise.value)
}
if (values.length == promises.length) {
resolve(values)
}
});
})
}
//Promise的静态方法,race
static race(promises) {
return new MyPromise((resolve, reject) => {
promises.forEach(promise => {
promise.then(value => {
resolve(value)
})
});
})
}
//Promise的静态方法,race
catch (onRejected) {
return this.then(null, onRejected)
}
}
async与await
-
通过async可以来创建一个异步函数。
-
异步函数的返回值会自动封装到一个Promise中返回。
js
async function fn() {
return 10;
}
let result = fn()
console.log(result)//Promise对象
result.then((result) => {
console.log(result)//10
})
/*等价于:
function fn(){
return new Promise((reslove,reject)=>{
reslove(10)
})
}*/
await
- Promise解决了异步调用中回调函数问题,虽然通过链式调用解决了回调地狱,但是链式调用太多以后还是不好看。
- 我们更希望以同步的方式去调用异步的代码。
- 当我们通过await去调用异步函数时,它会暂停代码的运行,直到异步代码执行有结果。(PromiseResult有值)时(不会影响执行栈中的同步代码),才会将结果(PromiseResult)返回。
注意:
- await只能用于 async声明的异步函数中,或es模块的顶级作用域中。
- await阻塞的只是异步函数内部的代码,不会影响外部代码。
- 通过await调用异步代码时,需要通过try-catch来处理异常。
- 当我们使用await调用函数后,当前函数后边的所有代码会在当前函数执行完毕后,被放入到微任务队里中【注意是执行完毕后,这也是await实现代码异步的关键】。
js
async function fn4() {
console.log(1)
await console.log(2) // await调用的是普通函数,不是异步函数,所以是在执行栈中完成的
await console.log(3)
console.log(4)
}
//等价于:
function fn5() {
return new Promise(resolve => {
console.log(1)
// 加了await
console.log(2)
resolve()
}).then(r => {
console.log(3)
}).then(r=>{
console.log(4)
})
}
fn5()
console.log(5)
// 12534
使用场景
- async函数内;
.mjs
文件内;- 网页里
<script type="module">
内部直接使用。