Promise学习笔记

一、一些基础知识

两种类型的回调函数

同步回调

同步回调立即执行,完全执行完了才结束,不会放入回调队列中

如数组遍历相关的回调函数、Promise 的 excutor 函数

const arr = [1,2,3]
arr.forEach(item=>{
    console.log(item)
})
console.log('forEach之后才输出')
输出:1 2 3 forEach之后才输出

异步回调

异步回调不会立即执行,会放入回调队列中将来执行

如定时器回调、ajax 回调、Promise 的成功/失败回调

js 的 Error 处理

错误类型/常见的内置错误

Error:所有错误的父类型

ReferenceError:引用的变量不存在,如 a is not defined

TypeError:数据类型不正确的错误,如 Cannot read property ‘xxx’ of undefined 或 xx.xxx is not a function

RangeError:数据值不在其所允许的范围内,如 递归中没有 return,则 RangeError:Maximun call stack size exceeded

SyntaxError:语法错误

错误处理

捕获错误:try{…}catch(error){…}

抛出错误:throw new Error(‘xxx’)

function func(){
    if(...){
    }else{
        throw new Error('出错') //这里只抛出错误,具体要怎么处理是在控制台输出还是弹窗由下一级处理
    }
}
try{
    func()
}catch(error){
    console.log(error.message) //这里捕获错误在控制台输出
}

错误对象

message 属性:错误相关信息

stack 属性:函数调用栈记录信息

二、Promise

Promise 是 ES6 引入的异步编程的新的解决方案,旧的解决方案是使用回调函数

Promise 是个构造函数,Promise 对象用来封装一个异步操作(如 fs 文件操作、数据库操作、ajax、定时器)并可以获取其成功或失败的结果值

Promise 最大的好处是可以解决回调地狱问题,并在指定回调和错误处理方面更灵活

promise 的状态改变

(1)pending 变为 resolved

(2)pending 变为 rejected

注意:只有上面这 2 中情况,且一个 promise 对象只能改变一次状态

无论变为成功或失败,都会有一个结果数据(value 或 reason)

promise运行流程

promise运行流程

为什么使用 Promise

(1)指定回调函数的方式更灵活

旧的使用回调函数必须在启动异步任务前指定

promise:启动异步任务(只要 new 创建了 promise 对象就启动了异步任务) => 返回 promise 对象 => 给 promise 对象绑定回调函数(甚至可以在异步任务结束后指定/多个)

(2)支持链式调用,可以解决回调地狱问题

回调地狱:回调函数嵌套调用,外部回调函数异步执行的结果是嵌套的回调函数执行的条件

回调地狱不便于阅读,也不便于异常处理

解决方案:promise 链式调用,还可异常传透链式调用出错了会找到最后一个 catch 处理异常

终极解决方案:async / await

使用 Promise

API

(1)Promise 构造函数 Promise(excutor){}

excutor 函数:执行器 (resolve,reject)=>{}
resolve 函数:内部定义成功时调用的函数 value => {}
reject 函数:内部定义失败时调用的函数 reason => {}

注意:excutor 会在 Promise 内部立即同步回调,异步操作在执行器中执行,then 也是同步执行的,但 then 里的回调函数异步执行

(2)Promise.protorype.then 方法 (onResolved,onRejected)=>{}

onResolved 函数:成功的回调函数 (value) => {}
onRejected 函数:失败的回调函数 (reason) => {}

注意:指定用于得到成功 value 的成功回调和用于得到失败 reason 的失败回调返回一个新的 promise 对象

(3)Promise.protorype.catch 方法 (onRejected)=>{}

onRejected 函数:失败的回调函数 (reason) => {}

注意:是 then() 的语法糖,相当于 then(undefined,onRejected)

(4)Promise.resolve 方法 (value)=>{}

value 是成功的数据或 promise 对象

如 const p = Promise.resolve(1) //返回一个成功值为 1 的 promise 对象
p.then(value=>{console.log(value)})

注意:该方法返回一个成功/失败的 promise 对象,返回一个成功/失败的 promise 对象的简洁语法

(5)Promise.reject 方法 (reason)=>{}

如 const p = Promise.reject(1) //返回一个失败值为 1 的 promise 对象
p.catch(reason=>{console.log(reason)})

注意:该方法返回一个失败的 promise 对象

(6)Promise.all 方法 (promises)=>{}

promises 包含 n 个 promise 的数组

如 const pAll = Promise.all([p1,p2,p3])
pAll.then(
    value=>{...},
    reason=>{...}
)

注意:该方法返回一个新的 promise,只有所有的 promise 都成功才成功,只要有一个失败了就直接失败

(7)Promise.race 方法 (promises)=>{}

promises 包含 n 个 promise 的数组

如 const pAll = Promise.race([p1,p2,p3])
pAll.then(
    value=>{...},
    reason=>{...}
)

注意:该方法返回一个新的 promise 对象,第一个完成的 promise 的结果状态就是最终的结果状态

基本使用

new Promise((resolve,reject)=>{    //执行器函数,里面执行异步操作任务
    setTimeout(()=>{
        if(...){
            resolve('成功')
        } else{
            reject('失败')
        }
    },1000)
})
p.then(
    value=>{  //接收成功的 value 数据 onResolved
        console.log(value)
    },
    reason=>{ //接收失败的 reason 数据 onRejected
        console.log(reason)
    }
)

如何改变 promise 状态

(1)resolve(value):若当前是 pendding 就变为 resolved

(2)reject(reason):若当前是 pendding 就变为 rejected

(3)抛出异常:若当前是 pendding 就变为 rejected

一个 promise 指定多个成功/失败回调函数(如多次对一个 Promise 对象调用 then),都会调用吗

当 promise 改变对应状态时都会调用

改变 promise 状态和指定回调函数谁先谁后?

(1)都有可能,正常情况下(执行器中是异步任务,resolve()/reject() 在异步任务中)先指定回调再改变状态同时执行回调函数,也可以先改变状态再指定回调

(2)如何先改状态再指定回调

1)再执行器中直接调用 resolve()/reject()
2)延迟更长时间才调用 then()

(3)什么时候才能得到数据?

1)若先指定回调,当状态发生改变时,回调函数就会调用,得到数据
2)若先改变状态,当指定回调时,回调函数就会调用,得到数据

如先指定回调再改变状态

new Promise((resolve,reject)=>{
    setTimeout(()=>{
        resolve(1) //后改变状态同时指定数据,异步执行回调函数
    },100)
}).then( //先指定回调函数,保存当前指定的回调函数
    value => {},
    reason =>{}
)

先改变状态再指定回调1

new Promise((resolve,reject)=>{
    resolve(1)  //先改状态同时指定数据
}).then( //后指定回调函数,异步执行回调函数
    value => {},
    reason =>{}
)

先改变状态再指定回调2

const p = new Promise((resolve,reject)=>{
    setTimeout(()=>{
        resolve(1)   //先改状态同时指定数据
    },100)
})
setTimeout(()=>{
    p.then(  //后指定回调函数
        value => {},
        reason =>{}
    )
},110)

promise.then() 返回的新 promise 的结果状态由什么决定?

简单表达:由 then() 指定的回调函数执行结果决定

详细表达:

(1)若抛出异常,新 promise 变为 rejected,reason 为抛出的异常
(2)若返回非 promise 的任意值,新 promise 变为 resolved,value 为返回的值
(3)若返回的是另一个新 promise,此 promise 的结果就会成为新 promise 的结果

new Promise((resolve,reject)=>{
    resolve(1)
}).then(
    value => {
        console.log('onResolved1',value)
    },
    reason =>{
        console.log('onRejected1',reason)
    }
)
.then(
    value => {
        console.log('onResolved2',value)
    },
    reason =>{
        console.log('onRejected2',reason)
    }
)

上述代码输出:onResolved1 1 onResolved2 undefined

new Promise((resolve,reject)=>{
    reject(1)
}).then(
    value => {
        console.log('onResolved1',value)
    },
    reason =>{
        console.log('onRejected1',reason)
    }
)
.then(
    value => {
        console.log('onResolved2',value)
    },
    reason =>{
        console.log('onRejected2',reason)
    }
)

上述代码输出:onRejected1 1 onResolved2 undefined

promise 如何串联多个操作任务?

(1)promise 的 then() 返回一个新的 promise,可以看成 then() 的链式调用

(2)通过 then 的链式调用串联多个同步/异步任务,其中异步任务要包在一个 promise 里

promise 异常传/穿透

(1)当使用 promise 的 then 链式调用时,可在最后指定失败的回调

当 then 中没有对失败时 reason 的处理时相当于默认执行 reason => {throw reason}

(2)前面任何操作出了异常,都会传到最后失败的回调中处理

中断 promise 链

当使用 promise 的 then 链式调用时,在中间中断,不再调用后面的回调函数

办法:在回调函数中返回一个 pendding 状态的 promise 对象

return new Promise(()=>{})

三、自定义 Promise

(function(window){
    function Promise(excutor){
        const self = this //将当前 promise 保存起来,否则调用 resolve 函数中的 this 为 window
        self.status = 'pending'
        self.data = undefined //用于存储结果数据
        self.callbacks = [] //每个元素:{onResolved(){},onRejected(){}}
        function resolve(value){
            if(self.status !== 'pending'){return }
            self.status = 'resolved'
            self.data = value
            //
            if(self.callbacks.length>0){
                setTimeout(()=>{ //放入队列中执行所有成功的回调
                        self.callbacks.forEach(callbacksObj => {
                        callbacksObj.onResolved(value)
                    })
                })
            }
        }
        function reject(reason){
            if(self.status !== 'pending'){return }
            self.status = 'rejected'
            self.data = reason
            //
            if(self.callbacks.length>0){
                setTimeout(()=>{ //放入队列中执行所有失败的回调
                        self.callbacks.forEach(callbacksObj => {
                        callbacksObj.onRejected(reason)
                    })
                })
            }
        }
        //立即同步执行 excutor
        try{
            excutor(resolve,reject)
        }catch(error){ //若执行器抛出异常,promise 对象变为 rejected 状态
            reject(error)
        }
    }
    //Promise 原型对象的 then,指定成功或失败的回调函数,返回新的 promise 对象
    Promise.prototype.then = function(onResolved,onRejected){
        onResolved = typeof onResolved === 'function' ? onResolved : value => value //向后传递成功的 value
        onRejected = typeof onRejected === 'function' ? onRejected : reason => {throw reason} //指定默认的失败回调(实现错误/异常传透的关键点)
        const self = this
        return new Promise((resolve,reject)=>{
            function handle(callback){ //调用指定的回调函数处理,并根据执行结果改变 return 的 promise 的状态
                try{
                    const result = callback(self.data)
                    if(result instanceof Promise){
                        result.then( //只有 .then 才能获取到 result 的结果
                            value =>resolve(value),
                            reason => reject(reason)
                        )
                        //或简写为 result.then(resolve,reject)
                    }else{
                        resolve(result)
                    }
                }catch(error){
                    reject(error)
                }
            }
            if(self.status === 'pending'){
                //当前为 pending 状态,将回调函数保存起来,保存的函数里会取调用所保存函数来改变 promise 状态
                self.callbacks.push({
                    onResolved(value){ //这里能接收个 value,但没用
                        handle(onResolved)
                    },
                    onRejected(reason){
                        handle(onRejected)
                    },
                })
            }else if(self.status === 'resolved'){
                //当前是 resolved 状态,异步执行 onResolve 并改变 return 的 promise 状态
                setTimeout(()=>{
                    handle(onResolved)
                })
            }else{
                setTimeout(()=>{
                    handle(onRejected)
                })
            }
        })

    }
    //Promise 原型对象的 catch,指定失败的回调函数,返回新的 promise 对象
    Promise.prototype.catch = function(onRejected){
        return this.then(undefined,onRejected)
    }
    //Promise 函数对象的 resolve 方法,返回一个指定结果的 promise
    Promise.resolve = function(value){
        return new Promise((resolve,reject)=>{
            if(value instanceof Promise){
                value.then(resolve,reject)
            }else{
                resolve(value)
            }
        })
    }
    //Promise 函数对象的 reject 方法,返回一个指定 reason 的失败的 promise
    Promise.reject = function(reason){
        return new Promise((resolve,reject)=>{
            reject(reason)
        })
    }
    //Promise 函数对象的 all 方法,返回一个 promise,只有当所有 promise 都成功时才成功,否则只要有一个失败就失败
    Promise.all = function(promises){
        const values = new Array(promises.length) //保存所有成功 value 的数组
        let resolvedCount = 0 //保存成功 promise 的数量
        return new Promise((resolve,reject)=>{
            //遍历获取每个 promise 的结果
            promises.forEach((p,index)=>{
                Promise.resolve(p).then( //Promise.resolve(p)是处理传入的数组中包含的纯数字
                    value=>{
                        resolvedCount++
                        values[index] = value
                        if(resolvedCount === promises.length){
                            resolve(values)
                        }
                    },
                    reason => {
                        reject(reason)
                    }
                )
            })
        })
    }
    //Promise 函数对象的 race 方法,返回一个 promise,其结果由第一个完成(注意不是指位置上的第一个)的 promise 决定
    Promise.race = function(promises){
        return new Promise((resolve,reject)=>{
            promises.forEach((p,index)=>{
                Promise.resolve(p).then(
                    value => {
                        resolve(value)
                    },
                    reason => {
                        reject(reason)
                    }
                )
            })
        })
    }
    window.Promise = Promise
})(window)

自定义 Promise 的其他方法

Promise 的 resolveDelay 和 rejectDelay,都返回一个 Promise 对象,它们在指定的时间后才确定结果

Promise.resolveDelay = function(value,time){
    return new Promise((resolve,reject)=>{
        setTimeout(()=>{
            if(value instanceof Promise){
                value.then(resolve,reject)
            }else{
                resolve(value)
            }
        },time)
    })
}

Promise.rejectDelay = function(reason,time){
    const self = this
    return new Promise((resolve,reject)=>{
        setTimeout(()=>{
            reject(reason)
        },time)
    })
}

Promise 的 class 版本

(function(window){
    class Promise{
        constructor(excutor){
            const self = this //将当前 promise 保存起来,否则调用 resolve 函数中的 this 为 window
            self.status = 'pending'
            self.data = undefined //用于存储结果数据
            self.callbacks = [] //每个元素:{onResolved(){},onRejected(){}}
            function resolve(value){
                if(self.status !== 'pending'){return }
                self.status = 'resolved'
                self.data = value
                //
                if(self.callbacks.length>0){
                    setTimeout(()=>{ //放入队列中执行所有成功的回调
                            self.callbacks.forEach(callbacksObj => {
                            callbacksObj.onResolved(value)
                        })
                    })
                }
            }
            function reject(reason){
                if(self.status !== 'pending'){return }
                self.status = 'rejected'
                self.data = reason
                //
                if(self.callbacks.length>0){
                    setTimeout(()=>{ //放入队列中执行所有失败的回调
                            self.callbacks.forEach(callbacksObj => {
                            callbacksObj.onRejected(reason)
                        })
                    })
                }
            }
            //立即同步执行 excutor
            try{
                excutor(resolve,reject)
            }catch(error){ //若执行器抛出异常,promise 对象变为 rejected 状态
                reject(error)
            }
        }
        //Promise 原型对象的 then,指定成功或失败的回调函数,返回新的 promise 对象
        then(onResolved,onRejected){
            onResolved = typeof onResolved === 'function' ? onResolved : value => value //向后传递成功的 value
            onRejected = typeof onRejected === 'function' ? onRejected : reason => {throw reason} //指定默认的失败回调(实现错误/异常传透的关键点)
            const self = this
            return new Promise((resolve,reject)=>{
                function handle(callback){ //调用指定的回调函数处理,并根据执行结果改变 return 的 promise 的状态
                    try{
                        const result = callback(self.data)
                        if(result instanceof Promise){
                            result.then( //只有 .then 才能获取到 result 的结果
                                value =>resolve(value),
                                reason => reject(reason)
                            )
                            //或简写为 result.then(resolve,reject)
                        }else{
                            resolve(result)
                        }
                    }catch(error){
                        reject(error)
                    }
                }
                if(self.status === 'pending'){
                    //当前为 pending 状态,将回调函数保存起来,保存的函数里会取调用所保存函数来改变 promise 状态
                    self.callbacks.push({
                        onResolved(value){ //这里能接收个 value,但没用
                            handle(onResolved)
                        },
                        onRejected(reason){
                            handle(onRejected)
                        },
                    })
                }else if(self.status === 'resolved'){
                    //当前是 resolved 状态,异步执行 onResolve 并改变 return 的 promise 状态
                    setTimeout(()=>{
                        handle(onResolved)
                    })
                }else{
                    setTimeout(()=>{
                        handle(onRejected)
                    })
                }
            })

        }
        //Promise 原型对象的 catch,指定失败的回调函数,返回新的 promise 对象
        catch(onRejected){
            return this.then(undefined,onRejected)
        }
        //Promise 函数对象的 resolve 方法,返回一个指定结果的 promise
        static resolve = function(value){
            return new Promise((resolve,reject)=>{
                if(value instanceof Promise){
                    value.then(resolve,reject)
                }else{
                    resolve(value)
                }
            })
        }
        //Promise 函数对象的 reject 方法,返回一个指定 reason 的失败的 promise
        static reject = function(reason){
            return new Promise((resolve,reject)=>{
                reject(reason)
            })
        }
        //Promise 函数对象的 all 方法,返回一个 promise,只有当所有 promise 都成功时才成功,否则只要有一个失败就失败
        static all = function(promises){
            const values = new Array(promises.length) //保存所有成功 value 的数组
            let resolvedCount = 0 //保存成功 promise 的数量
            return new Promise((resolve,reject)=>{
                //遍历获取每个 promise 的结果
                promises.forEach((p,index)=>{
                    Promise.resolve(p).then( //Promise.resolve(p)是处理传入的数组中包含的纯数字
                        value=>{
                            resolvedCount++
                            values[index] = value
                            if(resolvedCount === promises.length){
                                resolve(values)
                            }
                        },
                        reason => {
                            reject(reason)
                        }
                    )
                })
            })
        }
        //Promise 函数对象的 race 方法,返回一个 promise,其结果由第一个完成(注意不是指位置上的第一个)的 promise 决定
        static race = function(promises){
            return new Promise((resolve,reject)=>{
                promises.forEach((p,index)=>{
                    Promise.resolve(p).then(
                        value => {
                            resolve(value)
                        },
                        reason => {
                            reject(reason)
                        }
                    )
                })
            })
        }
    }
    window.Promise = Promise
})(window)

四、async 与 await

async

async 函数返回值为 promise 对象

promise 对象的结果由 async 函数执行的返回值决定

await 表达式

await 右侧的表达式一般为 promise 对象,但也可以是其他的值,若为其他值则返回该值本身

若表达式为 promise 对象,await 返回的是 promise 成功的值,若返回的 promise 失败,就会抛出异常,要得到失败的结果要用 try…catch

function fun(){
    return new Promise((resolve,reject)=>{
        reject(1)
    })
}
async function fun1(){
    try{
        const value = await fun()
    }catch(error){
        console.log('得到失败的结果',value)
    }
}

await 必须写在 async 函数中

五、JS 异步之宏队列与微队列

JS 中用来存储执行回调函数的队列包含 2 个不同特定的列队

宏队列:保存待执行的宏任务(一些回调函数),如定时器回调、DOM 事件回调、ajax 回调

微队列:保存待执行的微任务(一些回调函数),如 promise 的回调(如 then 等)、MutationObserver 的回调

JS 执行时会区别这 2 个队列

(1)JS 引擎首先必须先执行所有的初始化同步任务代码
(2)每次准备取出每个宏任务执行前,都要将所有的微任务一个个取出来执行

宏队列微队列

例子1

const first = () => (new Promise((resolve,reject)=>{
    console.log(3)
    let p = new Promise((resolve,reject)=>{
        console.log(7)
        setTimeout(()=>{
            console.log(5)
            resolve(6)
        },0)
        resolve(1)
    })
    resolve(2)
    p.then((arg)=>{
        console.log(arg)
    })
}))
first().then((arg)=>{
    console.log(arg)
})
console.log(4)

上述代码输出:3 7 4 1 2 5

例子2

setTimeout(()=>{
    console.log(0)
},0)
new Promise((resolve,reject)=>{
    console.log(1)
    resolve()
}).then(()=>{
    console.log(2)
    new Promise((resolve,reject)=>{
        console.log(3)
        resolve()
    }).then(()=>{
        console.log(4)
    }).then(()=>{
        console.log(5)
    })
}).then(()=>{
    console.log(6)
})
new Promise((resolve,reject)=>{
    console.log(7)
    resolve()
}).then(()=>{
    console.log(8)
})

宏队列:【0】

微队列:【2,8,4,6,5】

上述代码输出:1 7 2 3 8 4 6 5 0

注意:链式调用的 .then 何时放入微队列取决于链的上一个 then 啥时候执行,如轮到 4 执行后 5 才放入微队列,2 下一长串结束后 6 才放入微队列

注意:里层的多个链式调用 then 只要有一个放入队列后外层的 then 就可以放入队列

4 放入微队列后后面的 then 就执行了,只是 then 里的 5 等 4 执行了采访如微队列,这样即使 5 还没放入微队列,但 6 上面的 then 都执行完了,所以 6 可以放入微队列