前言
事件循环机制
由于 javascript 引擎是采用单线程运行机制,执行耗时过大的操作时会造成页面的阻塞,为了解决页面的阻塞问题,js 将任务分为 同步任务、异步任务,随之而来的是异步带来的执行顺序问题。
而 promise 的出现能为我们解决什么样的问题呢?
在传统的异步实现中,我们可以使用回调函数来实现异步编程,通过层层嵌套回调来满足这种异步的依赖关系,如果嵌套层数过多,可读性和可维护性都变得很差,产生所谓“回调地狱”,而Promise将回调嵌套改为链式调用,增加可读性和可维护性。
什么是回调函数?
把一个函数当作参数传递,传递的是函数的定义并不会立即执行,而是在将来特定的时机再去调用,这个函数就叫做回调函数。
在定时器setTimeout以及Ajax的请求时都会用到回调函数。
setTimeout(function(){ //这个function()就是回调函数,它只有在1秒后才会执行
console.log('执行了回调函数');
},1000)
在日常的工作中我们常常会碰到一下情况:
- 接口的调用。
- 请求网络资源(例如,文本文件、图像文件、二进制文件等)。
为了处理这些情况必须使用异步的操作,而回调是处理这些情况的一种方式,所以从本质上说回调函数是异步的。
回调地狱又是什么?
回调函数的层层嵌套被称之为 回调地狱
存在异步任务的情况下,为了确保异步任务能够按照顺序执行,我们将回调层层嵌套,此时就出现了回调地狱。回调地狱的出现会导致:
- 代码臃肿,可读性差
- 复用性差、扩展性差
- 高耦合、可维护性差
- 只能在回调中处理异步问题
setTimeout(function () { //第一层
console.log(111);
setTimeout(function () { //第二层
console.log(222);
setTimeout(function () { //第三层
console.log(333);
}, 1000)
}, 2000)
}, 3000)
如何避免回调地狱?
- promise
- async/await
- 使用 async.js 库: Async 是一个工具模块,它提供了直接、强大的函数来使用异步 JavaScript。
promise
什么是promise?
Promise 意为 '承诺',意思是在未来的某个时间点承诺返回数据给你。它是js中的一个原生对象,是一种异步编程的解决方案,可以替换掉传统的回调函数解决方案。
- Promise 是一个类,在执行这个类的时候会传入一个执行器,这个执行器会立即执行 。
- promise对象有三个状态:pending(进行中),resolved(已成功),rejected(已失败)
如何改变promise的状态:promise 通过 执行器的 resolve 和 reject 两个函数来更改状态:
-
- resolve(value): promise的状态由 pending ===>> resolved
- reject(error): promise的状态由 pending ===>> rejected
- 抛出异常: promise的状态由 pending ===>> rejected
- 一旦 promise 的状态发生变化后将不会再被改变
- then 对 promise 中返回结果进行处理,then会返回一个 新 的 Promise 实例,因此可实现链式调用。
promise本身只是一个容器, 真正异步的是它的两个回调 resolve() 和 reject()。
promise 的本质不是控制 异步代码的执行顺序(无法控制), 而是控制异步代码结果处理的顺序
因为异步代码的执行顺序是由事件循环机制决定的,我们无法直接控制它们的执行顺序。而 Promise 的作用是将异步操作封装为一个 Promise 对象,这个对象可以通过链式调用的方式来处理异步操作的结果。
所以 Promise 可以用来规定异步操作完成后所需执行的操作,从而控制异步代码结果处理的顺序。
创建一个 promise
1.new Promise(executor)
promise 状态只能在 promise 内部进行操作,通过 new Promise 创建 promise 对象时,Promise 必须接受一个回调函数作为参数,我们称该函数为执行器函数,promise 的内部操作在执行器函数中执行。
回调函数 executor:(resolve, reject) => {},也叫 “执行器”,执行器 executor 是同步执行的,只有 then() 里面的回调处理才是异步的,因为它需要等待主体任务执行结束
执行器函数又包含resolve和 reject两个参数。
- resolve : 将Promise对象的状态从 Pending(进行中) 变为 resolved(已成功)
- reject : 将Promise对象的状态从 Pending(进行中) 变为 rejected(已失败),并抛出错误
let promise = new Promise((resolve,reject)=>{
// 接收一个callback。参数是成功函数与失败函数
setTimeout(()=>{
let num = parseInt(Math.random()*100);
// 如果数字大于50就调用成功的函数,并且将状态变成Resolved
if(num > 50){
resolve(num);
}else{
// 否则就调用失败的函数,将状态变成Rejected
reject(num)
}
},10000)
})
2.Promise.resolve
除了使用 new 实例化 promise 外还可以使用 Promise.resolve(value) 返回一个 promise 对象,可以对返回值进行.then调用
- Promise.resolve(value) 方法返回一个以给定值解析后的Promise 对象。
- 如果这个值是一个 promise ,那么将返回这个 promise ;
- 如果这个值是thenable(即带有"then"方法),返回的promise会“跟随”这个thenable的对象,采用它的最终状态;否则返回的promise将以此值完成。
Promise.resolve(11).then((value)=>{console.log(value)})
Promise.resolve() 返回任意一个非 promise 的值都会被包裹成 promise 对象
Promise.resolve().then(() => {
return new Error('error!!!')
}).then(res => {
console.log("then: ", res)
}).catch(err => {
console.log("catch: ", err)
})
// ----------------answer------------------
"then: " "Error: error!!!"
由于 Promise.resolve 会将任何返回值包裹成 promise 对象,
所以 return new Error('error!!!') 也被包裹成了return Promise.resolve(new Error('error!!!')) 因此不会被catch捕获。
3.Promise.reject
Promise.reject() 方法返回一个带有拒绝原因的Promise对象。
Promise.reject(value) 也是实例化 promise 的一种快捷方式,但是最终返回的 promise 的状态为 rejected
Promise.reject(new Error('error!!!'));
Promise 的实例方法
then
promise.then(resolved, rejected)
then 接收两个函数
- 第一个是 promise 的状态转变为 resolved 时调用的回调函数 resolved()
- 第二个是 promise 转变为 rejected 是调用的回调函数 rejected()
then 会返回一个新的 promise 对象,将 then 的返回值包装成 Promise,所以 then 方法支持 链式调用
只有 promise 在执行了 resolve 之后,才会触发 then 回调函数 的执行
promise.resolve(1).then(res=>{
console.log(res);
//在构造函数中如果你执行力resolve函数就会到这一步
},err=>{
// 执行了reject函数会到这一步
console.log(err);
})
catch
该方法相当于 then 方法的第二个参数,指向 reject 的回调函数。会返回一个新的 promise
不过 catch 方法还有一个作用,就是在执行 resolve 回调函数时,如果出现错误,抛出异常,不会停止运行,而是进入 catch 方法中。
catch 不管在哪里都能捕获到上层未捕获的错误
能被 catch 捕获的两种情况
- throw new Error
- Promise.reject()
例如:
Promise.resolve().then(() => {
return new Error('error!!!')
}).then(res => {
console.log("then: ", res)
}).catch(err => {
console.log("catch: ", err)
})
// ----------------answer------------------
"then: " "Error: error!!!"
.then 或者 .catch 中 return 一个 error 对象并不会抛出错误,Promise.resolve() 将抛出的对象包装成Promise.resolve(new Error('error!!!')), 所以不会被后续的 .catch 捕获。
修改以上代码,使得 'error!!!' 抛出时能被catch 捕获可以使用以下两种方法:
return Promise.reject(new Error('error!!!'));
// or
throw new Error('error!!!')
听说在捕获到异常后还能够继续then,那就来试试吧
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(10)
}, 1000)
}).then(() => {
throw Error("1123")
}).catch((err) => {
console.log(err);
})
.then(() => {
console.log('异常捕获后可以继续.then');
})
果然如此 当第一个 .then 的异常被捕获后 catch 会返回一个新的 promise 所以 后续的then 仍能够继续执行。
Error: 1123
"异常捕获后可以继续.then"
finally
finally方法用于指定不管 Promise 对象最后状态如何,都会执行的操作
- .finally()方法不管Promise对象最后的状态如何都会执行
- finally 的回调函数不接受任何参数,因此无法判断前面的 Promise 状态到底是resolved还是 rejected。finally方法里面的操作,不依赖于 Promise 的执行结果,与状态无关的。
- 它最终返回的默认会是一个上一次的Promise对象值,不过如果抛出的是一个异常则返回异常的Promise对象。
Promise.resolve('1')
.then(res => {
console.log(res)
})
.finally(() => {
console.log('finally')
})
Promise.resolve('2')
.finally(() => {
console.log('finally2')
return '我是finally2返回的值'
})
.then(res => {
console.log('finally2后面的then函数', res)
})
// ----------------answer------------------
'1'
'finally2'
'finally'
'finally2后面的then函数' '2'
若是 finally 中抛出的是一个异常会怎样处理
function promise1 () {
let p = new Promise((resolve) => {
console.log('promise1');
resolve('1')
})
return p;
}
function promise2 () {
return new Promise((resolve, reject) => {
reject('error')
})
}
promise1()
.then(res => console.log(res))
.catch(err => console.log(err))
.finally(() => console.log('finally1'))
promise2()
.then(res => console.log(res))
.catch(err => console.log(err))
.finally(() => console.log('finally2'))
// ----------------answer------------------
promise1'
'1'
'error'
'finally1'
'finally2'
值透传
值穿透指的是,链式调用的参数不是函数时,会发生值穿透,就传入的非函数值忽略,传入的是之前的函数参数。
.then 或者 .catch 的参数期望是函数,传入非函数则会发生值透传。
Promise.resolve(1)
.then(2)
.then(Promise.resolve(3))
.then(console.log)
// ----------------answer------------------
1
只有传入的是函数时才会传递给下一个链式调用
Promise.resolve(1)
.then(function () {
return 2
})
.then(() => { Promise.resolve(3) })
.then(console.log)
// ----------------answer------------------
undefined
此时的箭头函数没有返回值,所以 console 的结果为 undefined。
小结
-
.then方法是能接收两个参数的,第一个是处理成功的函数,第二个是处理失败的函数,也可以认为catch是then的第二个参数的简便写法。
-
.then 和 .catch 都会返回一个新的 Promise
-
.then 或 .catch 返回的值不能是 promise 本身,否则会造成死循环
-
.then 或者 .catch 的参数期望是函数,传入非函数则会发生值透传。.
Promise 的静态方法
Promise.all()
Promise.all 并发的执行一组异步任务,并且在所有异步操作执行完后才执行回调。
Promise.all() 接收一个数组,数组的每一项都是一个promise对象,会返回一个 Promise 实例。
当数组中所有的 promise 的状态都达到 resolved 的时候,all 方法的状态就会变成 resolved,
如果有一个状态变成了 rejected,那么 all 方法的状态就会变成 rejected,失败原因的是第一个失败 promise 的结果。
function runAsync (x) {
const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000))
return p
}
Promise.all([runAsync(1), runAsync(2), runAsync(3)])
.then(res => console.log(res))
// -------------answer---------------------------
1
2
3
[1, 2, 3]
.all() 后面的 .then() 里的回调函数接收的就是所有异步操作的结果。
结果中数组的顺序与 Promise.all() 接收到的数组顺序一致
function runAsync (x) {
const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000))
return p
}
function runReject (x) {
const p = new Promise((res, rej) => setTimeout(() => rej(`Error: ${x}`, console.log(x)), 1000 * x))
return p
}
Promise.all([runAsync(1), runReject(4), runAsync(3), runReject(2)])
.then(res => console.log(res))
.catch(err => console.log(err))
// ----------------answer-------------
// 1s后输出
1
3
// 2s后输出
2
Error: 2
// 4s后输出
4
catch 是会捕获最先的那个异常,在这道题目中最先的异常就是 runReject(2) 的结果,
Error: 4 将不会被 catch 捕获,而是会进入 then 的第二个参数。
另外,如果一组异步操作中有一个异常都不会进入 then() 的第一个回调函数参数中。
Promise.all([runAsync(1), runReject(4), runAsync(3), runReject(2)])
.then(res => console.log(res),
err => console.log(err))
Promise.race()
Promise.race() 将一组异步任务中最先完成或失败的 Promise 返回成一个新的Promise。
如果第一个 promise 对象状态变成 resolved,那自身的状态变成了resolved;
反之第一个 promise 变成rejected,那自身状态就会变成 rejected
const p1 = new Promise((resolve, reject) => {
setTimeout(resolve, 500, 1)
})
const p2 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 2)
})
Promise.race([p1, p2]).then((value) => {
console.log(value) // 2
})
Promise.race([p1, p2, 3]).then((value) => {
console.log(value) // 3
})
最先执行完成的,就执行相应后面的.then或者.catch。谁先以谁作为回调
function runAsync (x) {
const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000))
return p
}
Promise.race([runAsync(1), runAsync(2), runAsync(3)])
.then(res => console.log('result: ', res))
.catch(err => console.log(err))
// ----------------answer-------------
1
'result: ' 1
2
3
它只会获取最先执行完成的那个结果,其它的异步任务虽然也会继续进行下去,不过race已经不管那些任务的结果了。
race() 后面的 .then 只会执行第一个完成的 promise 回调,其他promise 仍继续 但不会再进入到 .then
function runAsync(x) {
const p = new Promise(r =>
setTimeout(() => r(x, console.log(x)), 1000)
);
return p;
}
function runReject(x) {
const p = new Promise((res, rej) =>
setTimeout(() => rej(`Error: ${x}`, console.log(x)), 1000 * x)
);
return p;
}
Promise.race([runReject(0), runAsync(1), runAsync(2), runAsync(3)])
.then(res => console.log("result: ", res))
.catch(err => console.log(err));
// ----------------answer-------------
0
Error: 0
1
2
3
runReject(0) 最先执行完,抛出错误 rej(Error: 0) 所以进入了 catch() 中,Promise.race().catch执行完成,将不再执行,而 runAsync(1), runAsync(2), runAsync(3) 仍继续执行。
Promise.any()
Promise.any() 返回一组异步任务中最快的成功结果,如果全部失败就返回失败结果。
接收一个数组,数组的每一项都是一个promise对象,该方法会返回一个新的 promise,数组内的任意一个 promise 变成了resolved状态,那么由该方法所返回的 promise 就会变成resolved状态;
如果数组内的 promise 状态都是rejected,那么该方法所返回的 promise 就会变成rejected状态
all、race、any的区别
all: 成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值。
race: 哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。
any: 返回最快的成功结果,如果全部失败就返回失败结果。
async/await
async/await 是 Generator的语法糖,对异步操作的一种封装,用同步方式,执行异步操作。
async 用于申明一个 function 是异步的,而 await 用于等待一个异步方法执行完成,用同步的方法执行异步操作。
function foo(num) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(num * 2)
}, 1000)
})
}
async function asyncFn() {
const num1 = await foo(1)
const num2 = await foo(num1)
const num3 = await foo(num2)
return num3
}
asyncFn().then(res => console.log(res))
async
async 声明一个异步函数,返回的是一个Promise对象,async 函数内部 return 的返回值,会成为 then 回调函数的参数。
async 是位于 function 前面的一个前缀,只有 async 函数中,才能使用 await。
async 执行完之后会返回一个 promise 的对象:
- 若返回值刚好是一个 promise 对象则直接返回;
- 若返回值不是 Promise 对象,会将对应的结果包装成 promise 对象,相当于 Promise.resolve();
- 若没有返回值则会返回 undefined 的 promise 对象 。
因此 async 修饰的函数还能继续使用 .then 进行链式调用。
async function fn () {
// return await 1234
// 等同于
return 123
}
fn().then(res => console.log(res)) // 123
await
await 后面跟着的是 promise 的异步操作,await 会阻塞后面的代码,直到 promise 的完成并返回其处理结果,await 后面
- 可以是普通值
- 可以是 thenable
- 也可以是 Promise 主动调用 resolve 或者 reject
当 promise 的状态变为 resolved 时才会执行 await 后续的代码;
function request(num) { // 模拟接口请求
return new Promise(resolve => {
setTimeout(() => {
resolve(num * 2)
}, 1000)
})
}
async function fn () {
const res1 = await request(5)
const res2 = await request(res1)
console.log(res2) // 2秒后输出 20
}
fn()
等 request(5) 执行完之后再继续下一个 request(res1) 的请求,在 async 函数中,await 规定了异步操作只能一个一个排队执行,从而达到 用同步方式,执行异步操作 的效果。
如果 await 后面跟的不是 promise ,await 后面接的不是 Promise 的话,会通过 promise.resolve() 将其转换成 Promise。
function request(num) { // 去掉Promise
setTimeout(() => {
console.log(num * 2)
}, 1000)
}
async function fn() {
await request(1) // 2
await request(2) // 4
}
fn()
// 1秒后执行完 同时输出 2 4
而本案例的 setTimeout 失去了排队的效果 是因为 async/await 本身是会串行执行微任务的,
错误处理
如果 await 后面的异步操作出错,那么等同于 async 函数返回的 Promise 对象被 reject。
需要捕获 rejected 则需要在函数内部 使用 try catch 或者链式调用 .catch 操作。
async/await
使用语法:
- 函数前面使用 async 修饰
- async 函数内部 用 await 来修饰 promise 操作
- await只能在async函数中使用 不然会报错
作用:用同步方式,执行异步操作
async/await 实现同步执行异步操作
async function asyncFn() {
const num1 = await fn(1)
console.log(num1) // 2
const num2 = await fn(num1)
console.log(num2) // 4
const num3 = await fn(num2)
console.log(num3) // 8
return num3
}
const asyncRes = asyncFn()
console.log(asyncRes) // Promise
asyncRes.then(res => console.log(res)) // 8
手写实现
Promise 实现原理
const promise = new Promise((resolve, reject) => {
resolve('success')
reject('err')
})
promise.then(value => {
console.log('resolve', value)
}, reason => {
console.log('reject', reason)
})
// 输出 resolve success
通过以上例子可以分析出 promise 的实现:
- promise 是一个类,实例化 promise 时传入执行器,通过执行对应的函数完成 promise 的状态转变:
- promise的 初始状态为pending;
- 执行 resolve 转变为 resolved(fulfilled);
- 执行 reject 状态转变为 rejected;
- 执行过程中抛出错误 throw error时状态转变为 rejected。
- 状态发生一旦变化则不会再改变,一次即永久。
- then 的链式调用,接收 promise 的最终结果,通过状态判断需要执行的回调函数
- 如果状态成功,则调用成功的回调函数
- 如果状态失败,则调用失败的回调函数
resolve、reject的实现
- 新建 MyPromise 初始化状态传入执行器
promise 的状态、 promise最终结果
执行器 executor:(resolve, reject) => {}
class MyPromise{
constructor(executor){
// 1. 初始化值
this.promiseStatus = 'pending' // promiseStatus 的状态,初始化时为 pending
this.promiseResult = null // promise 的结果
executor() // 接受实例化 MyPromise 时传入的执行函数,会立即执行
}
}
- 向执行器中传入 resolve、reject
在函数 resolve reject 修改 promise 的状态,并修改变化后的值,而在调用 resolve / reject 时要确定 this 的指向。
如果 执行器中 直接调用 this.resolve() 的话,resolve函数(普通函数) 中的 this 指向的是 window 或者undefined。
为了防止随着函数执行环境的变化而变化,要将resolve、reject函数中 this 指向 MyPromise 实例。
方法一: 通过 .bind(this)
class MyPromise{
constructor(executor){
// 1. 初始化值
this.promiseStatus = 'pending' // promiseStatus 的状态,初始化时为 pending
this.promiseResult = null // promise 的结果
// 2. 初始化 resolve、reject 的 this 指向
// resolve 和 reject 的 this 要指向永远指向当前的 MyPromise 实例, 否则 resolve 中的 this丢失
// 通过 bind(this) 将函数的 this 指向实例,防止随函数执行环境的变化而变化
// 或者将 resolve、 reject 函数写成箭头函数的方式
this.resolve = this.resolve.bind(this)
this.reject = this.reject.bind(this)
executor(this.resolve,this.reject) // 接受实例化 MyPromise 时传入的执行函数,会立即执行
}
// 如果直接调用的话,普通函数this指向的是window或者undefined
// 所以在构造器中通过 bind(this) 改变 this 指向
resolve(value) {
// 如果没有 bind(this), 函数内部的 this 会丢失为 undefined
// console.log(this);
this.promiseStatus = 'resolved'
this.promiseResult = value
}
reject(reason) {
this.promiseStatus = 'rejected'
this.promiseResult = reason
}
}
方法二:采用 回调函数的形式 让 函数中的 this 指向当前实例对象
class MyPromise{
constructor(executor){
// 1. 初始化值
this.promiseStatus = 'pending' // promiseStatus 的状态,初始化时为 pending
this.promiseResult = null // promise 的结果
executor(this.resolve,this.reject) // 接受实例化 MyPromise 时传入的执行函数,会立即执行
}
// 用箭头函数就可以让this指向当前实例对象
resolve=(value)=>{
this.promiseStatus = 'resolved'
this.promiseResult = value
}
reject=(reason)=> {
this.promiseStatus = 'rejected'
this.promiseResult = reason
}
}
此时可以测试一下 MyPromise 的实现是否成功
const test1 = new MyPromise((resolve,reject)=>{
resolve('success!')
reject('error!')
})
console.log('test1 ===>> ',test1);
运行代码情况如下:
而我们发现此时的状态为 rejected ,并不是第一个 resolve 成功的结果, 说明并没有保证状态变更的唯一性
- 保证 promise 的状态不可变
为了确保 promise 的状态只变更一次,则要在 resolve 中加以限制,只有在状态为pending 是才能发生改变。
resolve(value) {
if(this.promiseStatus === 'pending'){
this.promiseStatus = 'resolved'
this.promiseResult = value
}
}
reject(reason) {
if(this.promiseStatus === 'pending'){
this.promiseStatus = 'rejected'
this.promiseResult = reason
}
}
- throw
而当执行器中 抛出异常时该如何捕获呢?
其实很简单只要在 executor 处使用 try/catch 即可,当抛出异常时使用 reject() 改变 promise 的状态
try{
executor(this.resolve,this.reject) // 接受实例化 MyPromise 时传入的执行函数,会立即执行
} catch(e){
// 当捕获到错误时执行 reject
this.reject(e)
}
then 的实现
then 的使用方法
promise.resolve(1).then(res=>{
console.log(res);
//在构造函数中如果你执行力resolve函数就会到这一步
},err=>{
// 执行了reject函数会到这一步
console.log(err);
})
then 的基本原理:
- 在使用 then 使通常提供两个回调,一个成功的回调,一个失败的回调
- 通过 promise 的状态来选择当前调用的回调;
- 如果 resolve、reject 中包含定时器,则在定时器结束后执行 then;
- then 返回的是一个新的 promise包装对象,由此实现了 then 的链式调用
then 的简单实现
接收两个回调,并通过 promise 的状态选择执行的回调
then(onFulfilled, onRejected) {
// 判断状态
if (this.promiseStatus === 'resolved') {
// 调用成功回调,并且把值返回
onFulfilled(this.promiseResult);
} else if (this.promiseStatus === 'rejected') {
// 调用失败回调,并且把原因返回
onRejected(this.promiseResult);
}
}
定时器
当我们在 resolve 中遇到定时器时,then会怎么处理呢?
const test = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('成功') // 1秒后输出 成功
}, 1000)
}).then(res => console.log('resolve',res), err => console.log('reject',err))
我们不能确保 1秒 后才执行 then 函数,但是我们可以保证 1秒 后再执行 then 里的回调。
在遇到定时器的时候我们要将 then 的两个回调函数保存起来,当 promise 的状态发生改变时再去从数组中取出对应的回调,并根据 promise 的状态判断执行哪个回调。
由于 then 支持链式调用,所以采用数组的形式来保存每一次的回调函数
class MyPromise{
constructor(executor){
// 1. 初始化值
this.promiseStatus = 'pending' // promiseStatus 的状态,初始化时为 pending
this.promiseResult = null // promise 的结果
// 2. 初始化 resolve、reject 的 this 指向
// resolve 和 reject 的 this 要指向永远指向当前的 MyPromise 实例, 否则 resolve 中的 this丢失
// 通过 bind(this) 将函数的 this 指向实例,防止随函数执行环境的变化而变化
// 或者将 resolve、 reject 函数写成箭头函数的方式
this.resolve = this.resolve.bind(this)
this.reject = this.reject.bind(this)
// then 的链式调用 并支持 定时器
this.onResolvedCallbacks = [] // 保存成功的回调
this.onRejectedCallbacks = [] // 保存失败的回调
try{
executor(this.resolve,this.reject) // 接受实例化 MyPromise 时传入的执行函数,会立即执行
} catch(e){
// 当捕获到错误时执行 reject
this.reject(e)
}
}
// 如果直接调用的话,普通函数this指向的是window或者undefined
// 所以在构造器中通过 bind(this) 改变 this 指向
resolve(value) {
// 如果没有 bind(this), 函数内部的 this 会丢失为 undefined
// console.log(this);
// 保证只有在 pending 状态时才能发生改变
if(this.promiseStatus === 'pending'){
this.promiseStatus = 'resolved'
this.promiseResult = value
}
while (this.onResolvedCallbacks.length) {
this.onResolvedCallbacks.shift()(this.promiseResult)
}
}
reject(reason) {
// 保证只有在 pending 状态时才能发生改变
if(this.promiseStatus === 'pending'){
this.promiseStatus = 'rejected'
this.promiseResult = reason
}
while (this.onRejectedCallbacks.length) {
this.onRejectedCallbacks.shift()(this.promiseResult)
}
}
then(onFulfilled, onRejected) {
console.log(onFulfilled)
// 判断状态
if (this.promiseStatus === 'resolved') {
// 调用成功回调,并且把值返回
onFulfilled(this.promiseResult);
} else if (this.promiseStatus === 'rejected') {
// 调用失败回调,并且把原因返回
onRejected(this.promiseResult);
}else if (this.promiseStatus === 'pending') {
// 如果状态为待定状态, 表示执行器还未执行完毕,暂时保存两个回调
this.onResolvedCallbacks.push(onFulfilled.bind(this))
this.onRejectedCallbacks.push(onRejected.bind(this))
}
}
}
const test = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('成功') // 1秒后输出 成功
}, 1000)
}).then(res => console.log('resolve',res), err => console.log('reject',err))
链式调用
then 能实现链式调用的根本原因是它的返回对象:
-
then 返回的是一个新的 promise 对象
-
then 不能返回自身,否则会造成死循环
-
如果返回值是 promise 对象,返回值为成功,新 promise 就是成功
-
如果返回值是 promise 对象,返回值为失败,新 promise 就是失败
-
如果返回值非 promise 对象,新 promise 对象就是成功,值为此返回值
// then 返回的是一个 promise 对象
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function'? onFulfilled : val=>val
onRejected = typeof onRejected === 'function'? onRejected : reason => {throw reason}
var thenPromise = new MyPromise((resolve, reject) => {
// 将参数 resolvePromise 赋值为一个函数
const resolvePromise = cb => {
try {
const x = cb(this.promiseResult)
if (x === thenPromise) {
// 不能返回自身哦
throw new Error('不能返回自身。。。')
}
if (x instanceof MyPromise) {
// 如果返回值是Promise
// 如果返回值是promise对象,返回值为成功,新promise就是成功
// 如果返回值是promise对象,返回值为失败,新promise就是失败
// 谁知道返回的promise是失败成功?只有then知道
x.then(resolve, reject)
} else {
// 非Promise就直接成功
resolve(x)
}
} catch (err) {
// 处理报错
reject(err)
throw new Error(err)
}
}
// 判断状态
if (this.promiseStatus === 'resolved') {
// 此时执行 resolvePromise() 并将成功的回调作为参数传入
resolvePromise(onFulfilled)
} else if (this.promiseStatus === 'rejected') {
// 执行 resolvePromise() 并将失败的回调作为参数传入
resolvePromise(onRejected)
}else if (this.promiseStatus === 'pending') {
// 如果状态为待定状态,暂时保存两个回调
this.onResolvedCallbacks.push(onFulfilled.bind(this))
this.onRejectedCallbacks.push(onRejected.bind(this))
}
})
return thenPromise
}
微任务
在 js 的运行机制中 then 是微任务。当遇到微任务时,将微任务放入任务队列排队,等当前的宏任务执行完毕再去任务队列中取出微任务执行。
then 身为微任务的特质,此时在 MyPromise 中该如何实现呢? 还是得通过定时器解决该问题。
只需要让 resolvePromise 函数异步执行就可以了
then(onFulfilled, onRejected) {
var thenPromise = new MyPromise((resolve, reject) => {
// resolvePromise 接收一个 函数
const resolvePromise = cb => {
setTimeout(() => {
try {
const x = cb(this.promiseResult)
if (x === thenPromise) {
// 不能返回自身哦
throw new Error('不能返回自身。。。')
}
if (x instanceof MyPromise) {
// 如果返回值是Promise
// 如果返回值是promise对象,返回值为成功,新promise就是成功
// 如果返回值是promise对象,返回值为失败,新promise就是失败
// 谁知道返回的promise是失败成功?只有then知道
x.then(resolve, reject)
} else {
// 非Promise就直接成功
resolve(x)
}
} catch (err) {
// 处理报错
reject(err)
throw new Error(err)
}
})
}
// 判断状态
if (this.promiseStatus === 'resolved') {
// 执行 resolvePromise 并将成功的回调作为参数传入
resolvePromise(onFulfilled)
} else if (this.promiseStatus === 'rejected') {
// 将失败的回调传入 resolvePromise 执行
resolvePromise(onRejected)
}else if (this.promiseStatus === 'pending') {
// 如果状态为待定状态,暂时保存两个回调
this.onResolvedCallbacks.push(onFulfilled.bind(this))
this.onRejectedCallbacks.push(onRejected.bind(this))
}
})
return thenPromise
}
验证微任务的执行结果
const p = new MyPromise((resolve, reject) => {
resolve(1)
}).then(res => console.log(res), err => console.log(err))
console.log(2)
// -------------------
2
1
all
由于该需求同时请求多个接口,并将获取的数据渲染:
// 各组件数据回填 需要判断 多指标的情况
const promiseList = this.selectedIndicatorIdList.map(indexId => {
return getIndicatorDataApi(indexId, filterParams)
})
// 获取指标数据
const resData = Promise.all(promiseList)
console.log(resData)
由图可知, promise.all 返回了一个状态成功的 promise 且含有成功结果的数组
- 接收一个 Promise 数组,数组中如有非 Promise 项,则此项当做成功
- 如果所有 Promise 都成功,则返回成功结果数组
- 如果有一个 Promise 失败,则返回这个失败结果
static all(promises) {
const result = []
let count = 0
return new MyPromise((resolve, reject) => {
const addData = (index, value) => {
result[index] = value
count++
if (count === promises.length) resolve(result)
}
promises.forEach((promise, index) => {
if (promise instanceof MyPromise) {
promise.then(res => {
addData(index, res)
}, err => reject(err))
} else {
addData(index, promise)
}
})
})
}
race
- 接收一个 Promise 数组,数组中如有非 Promise 项,则此项当做成功
- 哪个 Promise 最快得到结果,就返回那个结果,无论成功失败
static race(promises) {
return new MyPromise((resolve, reject) => {
promises.forEach(promise => {
if (promise instanceof MyPromise) {
promise.then(res => {
resolve(res)
}, err => {
reject(err)
})
} else {
resolve(promise)
}
})
})
}
allSettled
- 接收一个Promise数组,数组中如有非Promise项,则此项当做成功
- 把每一个Promise的结果,集合成数组,返回
static allSettled(promises) {
return new Promise((resolve, reject) => {
const res = []
let count = 0
const addData = (status, value, i) => {
res[i] = {
status,
value
}
count++
if (count === promises.length) {
resolve(res)
}
}
promises.forEach((promise, i) => {
if (promise instanceof MyPromise) {
promise.then(res => {
addData('fulfilled', res, i)
}, err => {
addData('rejected', err, i)
})
} else {
addData('fulfilled', promise, i)
}
})
})
}
any
any 与 all 相反
- 接收一个 Promise 数组,数组中如有非 Promise 项,则此项当做成功
- 如果有一个 Promise 成功,则返回这个成功结果
- 如果所有 Promise 都失败,则报错
static any(promises) {
return new Promise((resolve, reject) => {
let count = 0
promises.forEach((promise) => {
promise.then(val => {
resolve(val)
}, err => {
count++
if (count === promises.length) {
reject(new AggregateError('All promises were rejected'))
}
})
})
})
}
}
async/await 实现原理
上文我们说到 async/await 是 基于 generator 实现的语法糖,那么先来看一看究竟什么是 generator:
什么是 generator?
Generator 函数是 ES6 提供的一种异步编程解决方案,可以把 Generator 理解成一个状态机,封装了多个内部状态。执行 Generator 函数会返回一个遍历器对象该对象可以依次遍历 Generator 内部的每一个状态,为改变执行流程提供了可能,从而为异步编程提供解决方案
generator 与普通函数的区别是多了一个 * 号,并且只有在 generator 函数中才能使用 yield,当执行到 yield 时generator 的执行流会被挂起,通过 next() 方法能让 generator 切换到下一个状态,next() 执行后会返回一个对象,其中包含两个属性 value 和 done
- value: 暂停点后面接的值,也就是yield后面接的值
- done:是否generator函数已走完,没走完为false,走完为true
generator 基本用法
function* gen() {
yield 1
yield 2
yield 3
}
const g = gen()
console.log(g.next()) // { value: 1, done: false }
console.log(g.next()) // { value: 2, done: false }
console.log(g.next()) // { value: 3, done: false }
console.log(g.next()) // { value: undefined, done: true }
yield 函数时 next 返回值
当 generator 暂停点 yield 后接的是一个函数时,马上执行该函数并将函数的返回值 作为 yield 的对象 value 值
当 yield 后函数的返回值为 Promise 时,会将当前状态为 pending 的 promise 对象 作为 yield 的 value 值
function fn(num) {
return new Promise(resolve => {
console.log('执行resolve')
setTimeout(() => {
console.log('执行setTimeout')
resolve(num)
}, 1000)
})
}
function* gen() {
yield fn(1)
yield fn(2)
return 3
}
const g = gen()
console.log(g.next().value)
console.log(g.next().value)
console.log(g.next())
在执行 gen.next() 时会在将 yeild 之后的函数执行的返回值作为暂停点对象的value值
所以此时的执行结果为:
fn() 返回了一个promise,而 promise 的状态在定时器中才发生变更,所以前两个的 value 都是实例化时 promise 的状态 pending
若此时没有l定时器的干扰,promise 的状态会发生什么样的而变化呢?
function fn(num) {
return new Promise(resolve => {
console.log('执行resolve')
resolve(num)
})
}
function* gen() {
yield fn(1)
yield fn(2)
return 3
}
const g = gen()
console.log(g.next().value)
console.log(g.next().value)
console.log(g.next())
实例化 promise 时会执行resolve 回调,此时 next 返回的 promise 状态会从 pending 转变为 fulfilled (resolved)
next 传参
generate 可以用 next() 传递参数,通过 yield 来接收传递的值:
- 第一个 next() 传参无效,只有第二个开始传参才会生效
- 当 next 传入参数在上一次暂停的 yield 接收,在遇到下一个 yield 时会将 yield 后的返回值作为暂停点的对象返回
function* gen() {
const num1 = yield 1
console.log('gen===>',num1)
const num2 = yield 2
console.log('gen===>',num2)
return 3
}
const g = gen()
console.log('第一个')
console.log(g.next())
console.log('第二个')
console.log(g.next(11111))
console.log('第三个')
console.log(g.next(22222))
此时输出的结果为
第1次调用 g.next(),生成器函数开始执行,执行到第一个 yield 语句,返回 { value: 1, done: false }。
第2次调用 g.next(11111),生成器函数从上次暂停的地方继续执行,将传入的参数 11111 赋值给变量 num1,并打印出来,然后执行到第二个 yield 语句,返回 { value: 2, done: false }。
第3次调用 g.next(22222),生成器函数从上次暂停的地方继续执行,将传入的参数 22222 赋值给变量 num2,并打印出来,然后执行到函数末尾,返回 { value: 3, done: true }。
Promise + next 传参
当我们 Promise 与 next 传参相结合是会发生什么情况呢?
function fn(nums) {
return new Promise(resolve => {
setTimeout(() => {
resolve(nums * 2)
}, 1000)
})
}
function* gen() {
const num1 = yield fn(1) // 调用 g.next(res1)时 传入的参数会被 yield 接收 并赋值给 num1
const num2 = yield fn(num1)
const num3 = yield fn(num2)
return num3
}
const g = gen()
const next1 = g.next()
next1.value.then(res1 => {
console.log(next1) // 1秒后同时输出 { value: Promise { 2 }, done: false }
console.log(res1) // 1秒后同时输出 2
const next2 = g.next(res1) // 传入上次的res1 2
next2.value.then(res2 => {
console.log(next2) // 2秒后同时输出 { value: Promise { 4 }, done: false }
console.log(res2) // 2秒后同时输出 4
const next3 = g.next(res2) // 传入上次的res2
next3.value.then(res3 => {
console.log(next3) // 3秒后同时输出 { value: Promise { 8 }, done: false }
console.log(res3) // 3秒后同时输出 8
// 传入上次的res3
console.log(g.next(res3)) // 3秒后同时输出 { value: 8, done: true }
})
})
})
基于 generator 实现 async/await
async/await 的实现原理:
- async/await 自带执行器,不需要手动调用 next() 就能自动执行下一步
- async 函数返回值是 Promise 对象,而 Generator 返回的是生成器对象
- await 能够返回 Promise 的 resolve/reject 的值
generator 函数的 Promise+next 传参,就很像 async/await 了,区别在于
- gen 函数执行返回值不是 Promise,asyncFn 执行返回值是 Promise
- gen 函数需要执行相应的操作,才能等同于 asyncFn 的排队效果
- gen 函数执行的操作是不完善的,因为并不确定有几个 yield,不确定会嵌套几次
function generatorToAsync(generatorFn) {
return function() {
const gen = generatorFn.apply(this, arguments) // gen有可能传参
// 返回一个Promise
return new Promise((resolve, reject) => {
function go(key, arg) {
let res
try {
res = gen[key](arg) // 执行 next(), 获取执行返回的对象,有可能是reject状态的Promise
} catch (error) {
return reject(error) // 报错的话会走catch,直接reject
}
// 解构获得value和done
const { value, done } = res
if (done) {
// 如果done为true,说明走完了,进行resolve(value)
return resolve(value)
} else {
// 如果done为false,说明没走完,还得继续走
// value有可能是:常量,Promise,Promise有可能是成功或者失败
return Promise.resolve(value).then(val => go('next', val), err => go('throw', err))
}
}
go("next") // 第一次执行
})
}
}
使用 generatorToAsync函数 的版本
function* gen() {
const num1 = yield fn(1)
console.log(num1) // 2
const num2 = yield fn(num1)
console.log(num2) // 4
const num3 = yield fn(num2)
console.log(num3) // 8
return num3
}
const genToAsync = generatorToAsync(gen)
const asyncRes = genToAsync()
console.log(asyncRes) // Promise
asyncRes.then(res => console.log(res)) // 8