一、Promise(承诺) 概念
Promise基本概念
promise是js中进行异步编程新的解决方案,是用来封装异步操作并且获得其结果的方式。
在之前解决异步的方法是通过回调函数,但是在某些场景下,由于异步事件过多,便形成回调地狱,不利于阅读以及美感,从而为了解决回调地狱,引入了Promise方式。
同步与异步(了解)
首先,JS的运行是一个单线程的运行方式,而单线程运行就意味着在某些耗时的任务如IO操作、文件读取等就会出现进程阻塞,进程阻塞时页面会出现卡顿甚至卡死,这显然很不友好,所以为了解决这一问题就有了异步运行的存在。
所有的任务可以分成同步任务和异步任务,同步任务在主线程上按顺序执行(主线程执行时会形成一个执行栈),异步任务产生时会将异步任务放到消息队列(让异步任务排队执行的地方)里面执行。
当主线程所有同步任务执行完了之后,系统就会去看看消息队列中有哪些异步执行完成,然后系统会将执行完成的异步任务放到主线程执行栈中执行,执行完毕后再次去消息队列检查,不断重复即事件循环机制(EventLoop)。
总结:JavaScript的运行机制:只要主线程空了,就会去读取”消息队列”,消息队列又有任务队列与微任务队列
宏任务与微任务
在主线程执行的时候会生成一个执行栈,而执行栈中的代码便称为宏任务,而在当前宏任务执行完,下一次宏任务开始前执行的异步任务就是微任务,可以理解为宏任务中某些函数的回调事件。微任务会在当前宏任务执行后立即执行,但是微任务与微任务之间也需要排队。
宏任务:I/O setTimeOut、setInterval、setImmediate、requestAnimationFrame
微任务:process.nextTick、MutationObserver、Promise.then catch finally
如下图
类方法、实例方法、原型方法
class M {
//静态方法 只能类调用
static a(){}
//实例方法 只能实例调用
a = () => {}
//原型方法 实例和prototype都可调用
a(){}
}
复制代码
二、从手写 Promise 学习Promise
如何使用Promise
例子1:平常使用
let promise = new Promise((resolve,reject) =>{
//do something
//最终是成功还是失败
//成功 可以传值
resolve(value)
//失败 传入失败原因
reject(reason)
//注意:两者值执行一个
})
promise().then(success =>{
//执行成功,即resolve执行后 success 为 resolve传入的value值
console.log(success)
return success
},fail =>{
//执行失败,即reject执行后 fail 为 reject传入的reason
console.log(fail)
})
//.then里面会默认返回一个promise对象,并且可传递参数下去 如上面的return success 可由 suc 接收
.then( suc => console.log(suc))
.finally(()=>{
console.log('无论成功或者失败都会执行,并默认返回一个promise对象供链式调用')
})
.catch(err =>{
//捕捉promise运行中的错误,无论那里错误都能捕捉
console.log(err)
})
复制代码
例子2:通过async await 使用promise
function promise(){
return new Promise((resolve,reject) =>{
xxxxx
resolove('成功')
})
}
async function test(){
//此时会同步等待promise执行完成
let a = await promise()
console.log(a) // 成功
}
复制代码
例子3:Promise的其他类方法 all,race
function a(){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('a')
},1000)
})
}
function b(){
return 'b'
}
//接收参数 - 数组,数组内容为普通字符 或者一个Promise对象
//功能 - 会等待传入的所有内容执行完成后同时返回一个数组结果
Promise.all(['A','B',a(),b()]).then(res =>{
let result = res
console.log(result) //['A','B','a','b']
})
//接收参数 - 数组,数组内容为普通字符 或者一个Promise对象
//功能 - 会返回第一个完成的结果
Promise.race(['A','B',a(),b()]).then(res =>{
let result = res
console.log(result) // A
})
复制代码
手写Promise准备
在手写promise之前需要先弄清楚Promise的逻辑,具体应该明白它是什么,有什么,干什么。知己知彼,手到擒来!
- 首先使用promise 是通过new关键字可知道它是一个promise类,并且可以传入一个执行器(function),执行器带有两个参数resolve,reject。
- Promise 中有三种状态分别为成功fulfilled,失败rejected ,等待pending pending -> fulfilled pending -> rejected 一旦状态确定就不可更改
- resolve和reject函数是实例方法并且是用来更改状态的 resolve: fulfilled reject: rejected
- then方法内部做的事情就是判断状态,如果状态为成功,调用成功的回调函数,反之调用失败的回调函数,定义在原型对象中
- then 成功回调有一个参数,表示成功信息,失败回调有一个原因,表示失败原因
- then 方法链式调用 – 返回promise对象,并且promise可以次调用 .then()
- then 方法可传入普通值 和 promise对象,但是不能传递当前promise对象
- 捕获错误,抛出错误 所有执行函数的地方都需要trycatch捕获异常,并通过reject抛出
- 参数传递 如果then不传参数,自动传递给下一个then 判断then是否有参数,没有自行补充一个value => value
- finally 实例方法,无论成功失败,都要返回结果
- catch 捕捉promise链上任意一个地方的错误
- all和race两个类方法
- resolve类方法
简单实现
例1:编写一个简单的promise类,包好1,2,3,4,实现基本功能,不知道可不可以应付面试
//myPromise.js
//定义全局状态变量
const PENDING = 'pending'
const FULLFILLED = 'fulfilled'
const REJECTED = 'rejected'
//定义MyPromise类
class MyPromise {
constructor(execute){
//立即执行excute 并且传入实例方法resolve和reject
//这就是为什么promise里面的内容可以同步运行
execute(this.resolve,this.reject)
}
//定义状态,默认为等待
status = PENDING
//成功的结果
value = undefined
//失败的原因
reason = undefined
//实例方法
//把状态变为成功,并且具有不可逆性
resolve = (value) =>{
if(this.status !== PENDING) return
this.status = FULLFILLED
this.value = value
}
//把状态变为失败,并且具有不可逆性
rejecte = (reason)=>{
if(this.status !== PENDING) return
this.status = REJECTED
this.reason = reason
}
//原型方法,可传入两个回调函数,并且函数会接收一个参数,参数是resolve 和 reject 返回的值
then(successCallback,failCallback){
if(this.status === FULLFILLED){
successCallback(this.value)
}else if(this.status === REJECTED){
failCallback(this.reason)
}
}
}
//使用
let promsie = new MyPromise((resolve,reject) =>{
resolve('成功')
}).then(res =>{
console.log(res) //成功
})
复制代码
总结:
到这里就实现了一个简单Promise类,该类只实现了Promise的基本使用,即如何传参和通过.then()的方式获取结果,但是并没有实现异步编程的方法,所以目前只是有了初步架构,核心功能还没有实现。
完整实现
在简单实现的基础上,我们着重开始完善Promise的各个功能,即上面6 – 13点
6. then 方法链式调用 – 返回promise对象,并且promise可以次调用 .then()实现逻辑
(1)、解决promise的多次调用可以用successCallback和failCallback数组把.then里面的两个回调函数保存起来并通过while循环的方式执行
(2)、解决链式调用就需要在then函数里面返回一个Promise对象,并且链式调用可以传递参数,因此需要在新的promise中的resolve和reject中传递当前回调函数的执行结果 可以简写为
//value 和 reason 执行器里面resolve和reject执行传入的值
resolve(successCallback(this.value))
reject(failCallback(this.reason))
复制代码
具体如图:后续代码会因为其他功能更加完善,所以对此解决方法如图展示,不表示最终结果
7. then 方法可传入普通值 和 promise对象,但是不能传递当前promise对象 实现逻辑
(1)判断是否是promise,是的话还需要执行他的.then函数,如果是普通值直接返回。
(2)判断是否等于当前promise对象,是的话抛出类型错误(如果返回当前promise对象会出现死循环调用)
(3)根据以上两点 封装一个resolvePromise(promise2,x,resolve,reject) 函数来做处理
具体如图:后续代码会因为其他功能更加完善,所以对此解决方法如图展示,不表示最终结果
8. 捕获错误,抛出错误 所有执行函数的地方都需要trycatch捕获异常,并通过reject抛出 实现逻辑
在函数需要执行的地方通过try-catch的方式捕获异常并抛出异常
9. 参数传递 如果then不传参数,自动传递给下一个then 判断then是否有参数,没有自行补充一个value => value
10. finally 实例方法,无论成功失败,都要返回结果 实现逻辑
(1)、传入一个回调函数,没有参数,并且返回一个Promise供链式调用
(2)、虽然回调函数没有参数,但是他可以向下传递参数实则和.then相似
(3)、无论执行成功或者失败都会执行回调函数
11. catch 捕捉promise链上任意一个地方的错误 实现逻辑
直接调用this.then(undefined,callback)并返回
12. all和race两个类方法
(1)、传入参数数组形式,数组内容为普通值或者Promise对象
(2)、等待所有参数里面的Promise对象执行完成后返回一个数组结果/有一个参数执行完编结束返回一个值(race)
(3)、它是Promise的类方法,并且会返回一个Promise对象
13. resolve类方法 实现逻辑
(1)、传入一个普通值或者一个Promise对象
(2)、如果是普通值,将普通值转换成Promise对象返回,如果是Promise直接返回
最终代码
//myPromise.js
//定义全局状态变量
const PENDING = 'pending'
const FULLFILLED = 'fulfilled'
const REJECTED = 'rejected'
//定义MyPromise类
class MyPromise {
constructor(execute){
try{
//立即执行excute 并且传入实例方法resolve和reject
//这就是为什么promise里面的内容可以同步运行
execute(this.resolve,this.reject)
}catch(e){
this.reject(e)
}
}
//定义状态,默认为等待
status = PENDING
//成功的结果
value = undefined
//失败的原因
reason = undefined
//通过数组保存successCallback,并在resolve时循环调用
successCallback = []
//通过数组保存failCallback
failCallback = []
//实例方法
//把状态变为成功,并且具有不可逆性
resolve = (value) =>{
if(this.status !== PENDING) return
this.status = FULLFILLED
this.value = value
while(this.successCallback.length) this.successCallback.shift()(this.value)
}
//把状态变为失败,并且具有不可逆性
reject = (reason)=>{
if(this.status !== PENDING) return
this.status = REJECTED
this.reason = reason
while(this.failCallback.length) this.failCallback.shift()()
}
//原型方法,可传入两个回调函数,并且函数会接收一个参数,参数是resolve 和 reject 返回的值
then(successCallback,failCallback){
//判断是否有参数,如果没有自行加一个函数
successCallback = successCallback ? successCallback : value => value
failCallback = failCallback ? failCallback : reason => { throw reason }
let promise2 = new Promise((resolve,reject) =>{
//因为在使用 resolvePromise函数需要传入promise2,而此时promise2正在创建,所以需要异步操作,setTimeout的作用就是提供异步
if(this.status === FULLFILLED){
setTimeout(()=>{
try{
//获取成功回调结果并返回给下一个Promise
let x = successCallback(this.value)
resolvePromise(promise2,x,resolve,reject)
}catch(e){
reject(e)
}
},0)
}else if(this.status === REJECTED){
setTimeout(() =>{
try{
//获取失败回调结果并返回给下一个Promise
let x = failCallback(this.reason)
resolvePromise(promise2,x,resolve,reject)
}catch(e){
reject(e)
}
},0)
}else{
setTimeout(() =>{
//此时程序处于等待状态,所以需要将回调函数放入数组保存,等待resolve执行时再执行回调函数
this.successCallback.push(()=>{
try{
let x = successCallback(this.value)
resolvePromise(promise2,x,resolve,reject)
}catch(e){
reject(e)
}
})
this.failCallback.push(() =>{
try{
let x = failCallback(this.reason)
resolvePromise(promise2,x,resolve,reject)
}catch(e){
reject(x)
}
})
},0)
}
})
return promise2
},
finally(callback){
this.then(value =>{
//将传入的回调函数转换成Promise异步调用并且返回一个Promise对象供链式调用
//转换的目的就是需要等待当前回调函数执行完成后再进入链式调用
//finally的回调函数没有参数,但是会传递Promise链上的值
return MyPromsie.resolve(callback()).then(()=>value)
},reason =>{
return MyPromsie.resolve(callback()).then(()=>{throw reason})
})
},
catch(callback){
return this.then(undefid,callback)
},
static all(array){
let result = []
let index = 0
return new Promise((resolve,reject) =>{
//因为add需要调用resolve所以需要放在Promise内部
function add(key,value){
result[key] = value
index++
if(index === array.length){
resolve(result)
}
}
//首先循环遍历数组判断类型
for(let i=0;i<array.length;i++){
let current = array[i]
if(current instanceof MyPromise){
current.then((value)=>{add(i,value)},reason => add(add(i,reason)))
}else{
add(i,current)
}
}
})
}
static race(array){
let result = []
return new Promise((resolve,reject) =>{
//因为add需要调用resolve所以需要放在Promise内部
function add(key,value){
result[key] = value
resolve(result)
}
//首先循环遍历数组判断类型
for(let i=0;i<array.length;i++){
let current = array[i]
if(current instanceof MyPromise){
current.then((value)=>{add(i,value)},reason => add(add(i,reason)))
}else{
add(i,current)
}
}
})
}
static resolve(value){
if(value instanceof MyPromise) return value
return new Promise(resolve=>{ resoleve(value) })
}
}
//then方法内部回调函数执行结果比较函数
function resolvePromise(promise2,x,resolve,reject){
//首先做类型判断,如果相等,直接抛出类型错误
if(promise2 === x){
return reject(new TypeError('Chaining cycle detected for promise #<promise>'))
}
//是否是Promise判断
if(x instanceof MyPromise){
// Promise 对象 直接调用其then方法
//x.then(value => resolve(value),reason => reject(reason))
x.then(resolve,reject)
}else{
resolve(x)
}
}
module.export = MyPromise
复制代码
总结:
Promise是JS编程中的重中之重,强烈建议自行手写一次代码,弄清楚里面的各种功能实现也有助于提高异步编程能力。