Promise 一学就会

·  阅读 373

一、Promise(承诺) 概念

Promise基本概念

promise是js中进行异步编程新的解决方案,是用来封装异步操作并且获得其结果的方式。
在之前解决异步的方法是通过回调函数,但是在某些场景下,由于异步事件过多,便形成回调地狱,不利于阅读以及美感,从而为了解决回调地狱,引入了Promise方式。

同步与异步(了解)

首先,JS的运行是一个单线程的运行方式,而单线程运行就意味着在某些耗时的任务如IO操作、文件读取等就会出现进程阻塞,进程阻塞时页面会出现卡顿甚至卡死,这显然很不友好,所以为了解决这一问题就有了异步运行的存在。
所有的任务可以分成同步任务异步任务,同步任务在主线程上按顺序执行(主线程执行时会形成一个执行栈),异步任务产生时会将异步任务放到消息队列(让异步任务排队执行的地方)里面执行。 当主线程所有同步任务执行完了之后,系统就会去看看消息队列中有哪些异步执行完成,然后系统会将执行完成的异步任务放到主线程执行栈中执行,执行完毕后再次去消息队列检查,不断重复即事件循环机制(EventLoop)
总结:JavaScript的运行机制:只要主线程空了,就会去读取"消息队列",消息队列又有任务队列与微任务队列

宏任务与微任务

在主线程执行的时候会生成一个执行栈,而执行栈中的代码便称为宏任务,而在当前宏任务执行完,下一次宏任务开始前执行的异步任务就是微任务,可以理解为宏任务中某些函数的回调事件。微任务会在当前宏任务执行后立即执行,但是微任务与微任务之间也需要排队。 宏任务:I/O setTimeOut、setInterval、setImmediate、requestAnimationFrame 微任务:process.nextTick、MutationObserver、Promise.then catch finally
如下图

image.png

类方法、实例方法、原型方法

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的逻辑,具体应该明白它是什么,有什么,干什么。知己知彼,手到擒来!

  1. 首先使用promise 是通过new关键字可知道它是一个promise类,并且可以传入一个执行器(function),执行器带有两个参数resolve,reject。
  2. Promise 中有三种状态分别为成功fulfilled,失败rejected ,等待pending pending -> fulfilled pending -> rejected 一旦状态确定就不可更改
  3. resolve和reject函数是实例方法并且是用来更改状态的 resolve: fulfilled reject: rejected
  4. then方法内部做的事情就是判断状态,如果状态为成功,调用成功的回调函数,反之调用失败的回调函数,定义在原型对象中
  5. then 成功回调有一个参数,表示成功信息,失败回调有一个原因,表示失败原因
  6. then 方法链式调用 - 返回promise对象,并且promise可以次调用 .then()
  7. then 方法可传入普通值 和 promise对象,但是不能传递当前promise对象
  8. 捕获错误,抛出错误 所有执行函数的地方都需要trycatch捕获异常,并通过reject抛出
  9. 参数传递 如果then不传参数,自动传递给下一个then 判断then是否有参数,没有自行补充一个value => value
  10. finally 实例方法,无论成功失败,都要返回结果
  11. catch 捕捉promise链上任意一个地方的错误
  12. all和race两个类方法 (传入一个数组,内容为普通值或者Promise对象,前者是等待所有完成后返回一个统一结果,后者是只要有一个完成即返回结果
  13. 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))
复制代码

具体如图:后续代码会因为其他功能更加完善,所以对此解决方法如图展示,不表示最终结果

image.png 7. then 方法可传入普通值 和 promise对象,但是不能传递当前promise对象 实现逻辑
(1)判断是否是promise,是的话还需要执行他的.then函数,如果是普通值直接返回。
(2)判断是否等于当前promise对象,是的话抛出类型错误(如果返回当前promise对象会出现死循环调用)
(3)根据以上两点 封装一个resolvePromise(promise2,x,resolve,reject) 函数来做处理 具体如图:后续代码会因为其他功能更加完善,所以对此解决方法如图展示,不表示最终结果

image.png

image.png

8. 捕获错误,抛出错误 所有执行函数的地方都需要trycatch捕获异常,并通过reject抛出 实现逻辑 在函数需要执行的地方通过try-catch的方式捕获异常并抛出异常

image.png

9. 参数传递 如果then不传参数,自动传递给下一个then 判断then是否有参数,没有自行补充一个value => value

image.png

10. finally 实例方法,无论成功失败,都要返回结果 实现逻辑
(1)、传入一个回调函数,没有参数,并且返回一个Promise供链式调用
(2)、虽然回调函数没有参数,但是他可以向下传递参数实则和.then相似
(3)、无论执行成功或者失败都会执行回调函数

image.png

11. catch 捕捉promise链上任意一个地方的错误 实现逻辑 直接调用this.then(undefined,callback)并返回

12. all和race两个类方法
(1)、传入参数数组形式,数组内容为普通值或者Promise对象
(2)、等待所有参数里面的Promise对象执行完成后返回一个数组结果/有一个参数执行完编结束返回一个值(race)
(3)、它是Promise的类方法,并且会返回一个Promise对象

image.png

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 MyPromise((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{
                
                   //此时程序处于等待状态,所以需要将回调函数放入数组保存,等待resolve执行时再执行回调函数
                   this.successCallback.push(()=>{
                        try{
                            setTimeout(() =>{ 
                                 let x = successCallback(this.value)
                                resolvePromise(promise2,x,resolve,reject)
                            },0)
                        }catch(e){
                            reject(e)
                        }
                   })
                   this.failCallback.push(() =>{
                       try{
                           setTimeout(() =>{
                               let x = failCallback(this.reason)
                               resolvePromise(promise2,x,resolve,reject)
                           },0)
                       }catch(e){
                           reject(e)
                       }
                   })
            }
        })
        return promise2
    },
    finally(callback){
       return 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 MyPromise((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 => reject(reason))
                }else{
                    add(i,current)
                }
            }
        })
    }
    static race(array){
        let result = []
        return new MyPromise((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 => reject(reason))
                }else{
                    add(i,current)
                }
            }
        })
    }
    static resolve(value){
        if(value instanceof MyPromise) return value
        return new MyPromise(resolve=>{ resolve(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编程中的重中之重,强烈建议自行手写一次代码,弄清楚里面的各种功能实现也有助于提高异步编程能力。

分类:
前端
标签: