JS-Promise

1,880 阅读6分钟

promise前身

异步加载图片体验JS任务操作

        function loadImage(src, resolve, reject) {
            let image = new Image()
            image.src = src
            image.onload = () => {
                resolve(image)
            };
            image.onerror = reject
        }
        loadImage("images/1.jpg",(image) => {
            console.log('图片加载完成');
            document.body.appendChild(image)
        }, () => {
            console.log('图片加载失败');
        })
        console.log('sss');

定时器的任务轮询

    <style>
        div {
            width: 100px;
            height: 100px;
            background-color: aqua;
            left: 0;
            position: absolute;
        }
    </style>
    <div></div>
    <script>
        function interval(cb, delay = 100) {
            let id = setInterval(() => cb(id), delay);
        }
        // interval(function(id){
        //     console.log(id);
        // }, 1000)
        interval((timeId) => {
            const div = document.querySelector('div')
            let left = parseInt(window.getComputedStyle(div).left)
            console.log(left);
            div.style.left = left + 10 + 'px'
            if (left >= 200) {
                clearInterval(timeId)
                interval((timeId)=>{
                    let width = parseInt(window.getComputedStyle(div).width)
                    div.style.width = width - 10 + 'px'
                    if(width <= 20) {
                        clearInterval(timeId)
                    }
                }, 100)
            }
        })
        console.log('哈哈哈哈哈哈或');
        // for(let i = 0; i< 100000;i++){
        //     console.log(i);
        // }
        // 主线程执行完了,才会轮询任务队列
    </script>

通过文件依赖了解任务排序

    // hd.js
    function hd(){
        console.log('hd.js)
    }
    // houdunren.js
    function houdunren(){
        hd()
        console.log('houdunren.js)
    }
    /**
         * 先进先执行
         * 文件 --- 文件加载模块 --- 加载完 ---- 任务队列
         * 主进程执行完毕后,才会执行任务队列
         * 多个文件在文件加载模块中没有队列的特性,加载完毕就放入任务队列中,所以会有出错的情况,
         * houdunren.js中引用了hd.js,如果houdunren.js先执行完,那么会报错: houdunren.js:2 Uncaught ReferenceError: hd is not defined
         * */ 
        function load(src, resolve) {
            let script = document.createElement('script')
            script.src = src
            script.onload = resolve
            document.body.appendChild(script)
        }
        load('JS/hd.js', () => {
            hd()
        })
        load('JS/houdunren.js', () => {
            houdunren()
        })
        console.log('我先走一步');

-----> 解决

    function load(src, resolve) {
            let script = document.createElement('script')
            script.src = src
            script.onload = resolve
            document.body.appendChild(script)
        }
        load('JS/hd.js', () => {
            // hd.js加载完毕再去处理下面的
            load('JS/houdunren.js', () => {
                houdunren()
            })
        })
        console.log('我先走一步');

问题: 会存在大量的嵌套,导致回调地狱

promise微任务处理机制

        /**
         * pending 准备阶段
         * resolve 成功状态
         * reject  失败/拒绝状态
         * 
        */
        /**
         * 任务队列
         *    微任务队列
         *    宏任务队列
         * 先执行微任务,再执行宏任务
        */
        let p1 = new Promise((resolve, reject) => {
            // resolve('成功')
            reject('拒绝')
        }).then(res => {
            console.log(res);
        }, err => {
            console.log(err);
        })
        console.log(p1);

image.png

宏任务与微任务执行顺序

        /**
         * 同步任务----微任务-----宏任务
         * */
        setTimeout(() => {
            console.log('setTimeout');
        }, 0)
        new Promise((resolve, reject) =>{
            resolve()
            console.log('promise');
        }).then(res => console.log('成功'));
        console.log('东酥');

image.png

宏任务的提升误解

        let promise = new Promise((resolve, reject) => {
            /**
             * 微任务是宏任务执行完成才被创建
             * 
            */
            setTimeout(() => {
                resolve()
                console.log('settimeout2');
            }, 0);
            console.log('promise');
        }).then(res => console.log('成功'))
        console.log('dongsu');

image.png

Promise单一状态和状态中转

        let p1 = new Promise((resolve, reject) => {
            // resolve('成功')
            // reject('失败')
            setTimeout(() => {
                reject('失败')
            }, 2000);
        })
        new Promise((resolve, reject) => {
            // setTimeout(() => {
            //     resolve('fulfilled')
            // }, 1000);
            resolve(p1) // 下面的状态受p1状态的影响
        }).then(
            msg => {
                console.log(msg);
            },
            error => {
                console.log('error', error);
            }
        )
        console.log('dongsu');
        /**
         * 主线程----轮询微任务,执行-----
         * Promise状态改变才会产生微任务
         * promise只要状态改变,后面就是无法更改的
        */
        new Promise((resolve, reject) => {
            // setTimeout(() => {
            //     resolve('fulfilled')
            // }, 1000);
            resolve('fulfilled')
            reject('失败')
        }).then(
            msg => {
                console.log(msg);
            },
            error => {
                console.log('error', error);
            }
        )
        console.log('dongsu');

image.png

了解Promise.then的基本语法

一个promise需要提供一个then方法访问promise结果,then用于定义当promise状态发生改变时的处理,即promise处理异步操作then用于结果

  • then方法必须返回promise,用户返回或系统自动返回
  • 第一个函数在resolved状态时执行,即执行resolve时执行then第一函数处理成功状态
  • 第二个函数在rejected状态时执行,即执行reject时执行第二个函数处理失败状态,该函数时可选的
  • 两个函数都接收promise传出的值做为参数
  • 也可以使用catch来处理失败的状态
  • 如果then返回promise,下一个then会在当前promise状态改变后执行

链式调用

后盾人-链式调用

        /**
         * 主线程----微任务----宏任务
         * 
         * **/
        let p1 = new Promise((resolve, reject) => {
            // resolve('fulfilled')
            reject('rejected')
        })
        let p2 = p1.then(
            res => console.log(res),
            reason => console.log(reason)
        ).then(val => console.log('成功'), err => console.log(err))
        // console.log(p1);
        // console.log(p2);

        setTimeout(() => {
            console.log(p1);
            console.log(p2);
        }, 0);

then 是对上个promise 的rejected 的处理,每个 then 会是一个新的promise,默认传递 fulfilled状态

image.png

then返回值的处理技巧

        // let p1 = new Promise((resolve, reject) =>{
        //     resolve('fulfilled')
        // }).then(
        //     value => console.log(value),
        //     reason => console.log(reason)
        // )

        let p1 = new Promise((resolve, reject) => {
            resolve('fulfilled')
        }).then(
            value => {
                // 如果不return,下一个then就是对上一个then返回的promise处理,而不是下面的Promise处理
                // return 'dongsu' // 返回普通值,下一个then的参数可以直接接收到这个值
                // 独立的Promise发生错误,需要对其进行处理
                // new Promise((resolve, reject) => {
                //     setTimeout(() => {
                //         // resolve('处理完成')
                //         reject('处理错误')
                //     }, 0);
                // }).then(null, ()=>{})
                return new Promise((resolve, reject) => {
                    setTimeout(() => {
                        // resolve('处理完成')
                        reject('处理错误')
                    }, 0);
                }).then(null, () => { 
                    // return 'aaaa'
                    return new Promise((resolve, reject) => {
                        reject('失败了')
                    })
                 })
            },
            reason => console.log(reason)
        ).then(value => console.log('res1', value), err => console.log('err1', err))
        // 只要return Promise,后面的then就是对前面返回的promise的处理

其他类型的Promise封装

        let p1 = new Promise((resolve, reject) => {
            resolve('fulfilled')
        }).then(
            value => {
                // 类型1
                // return new Promise((resolve, reject) => {
                //     resolve('成功1')
                // })
                // 类型2
                // return {
                //     then(resolve, reject) {
                //         setTimeout(() => {
                //             resolve('这是对象2');
                //         }, 2000);
                //     }
                // }
                // 类型3
                // class DS {
                //     then(resolve, reject) {
                //         setTimeout(() => {
                //             resolve('这是对象3');
                //         }, 2000);
                //     }
                // }
                // return new DS()
                // 类型4
                return class {
                    static then(resolve, reject) {
                        // resolve('成功---这是一个静态方法')
                        resolve('失败---这是一个静态方法')
                    }
                }
            },
            reason => console.log(reason)
        ).then(
            value => console.log(value),
            reason => console.log(reason)
        )

使用promise封装ajax异步请求

// function ajax(url, cb) {
//     let xhr = new XMLHttpRequest();
//     xhr.open('GET', url);
//     xhr.send()
//     xhr.onload = function () {
//         if (this.status === 200) {
//             cb(JSON.parse(this.response))
//             // console.log(this.response);
//         } else {
//             throw new Error('加载失败')
//         }
//     }
// } 

function ajax(url) {
    return new Promise((resolve, reject) => {
        let xhr = new XMLHttpRequest();
        xhr.open('GET', url);
        xhr.send()
        xhr.onload = function () {
            if (this.status === 200) {
                resolve(JSON.parse(this.response))
            } else {
                reject('加载失败')
            }
        }
        xhr.onerror = function () {
            reject(this)
        }
    })
} 

catch

后盾人-catch

        // new Promise((resolve, reject) => {
        //     resolve('fulfilled')
        //     // reject('rejected')
        //     // reject(new Error('promise fail'))
        //     // throw new Error('错误')
        //     // hd + 1;

        // }).then(
        //     value => {
        //         return new Promise((resolve, reject) => {
        //             // hd+2;
        //             // resolve('成功')
        //             reject('失败')
        //         })
        //     },
        //     reason => console.log('rea1', reason)
        // ).then(
        //     value => console.log('val2', value),
        //     reason => console.log('rea2', reason)
        // )
        new Promise((resolve, reject) => {
            resolve('fulfilled')
            // reject('rejected')
            // reject(new Error('promise fail'))
            // throw new Error('错误')
            // hd + 1;

        }).then(
            value => {
                return new Promise((resolve, reject) => {
                    // hd+2;
                    // resolve('成功')
                    reject('失败')
                })
            },
        ).then(
            value => console.log('val2', value)
        ).catch(error => {
            console.log('err', error);
        })
  • 将 catch 放在最后面用于统一处理前面发生的错误

自定义错误处理

    // console.dir(Error);
// let err = new Error('格式错误')
// console.log(err);
class ParamError extends Error {
    constructor(msg) {
        super(msg)
        this.name = 'ParamError'
    }
}
class HttpError extends Error {
    constructor(msg) {
        super(msg)
        this.name = 'HttpError'
    }
}
function ajax(url) {
    return new Promise((resolve, reject) => {
        if (!/^http/.test(url)) {
            throw new ParamError('请求地址格式错误')
        }
        let xhr = new XMLHttpRequest();
        xhr.open('GET', url);
        xhr.send()
        xhr.onload = function () {
            if (this.status === 200) {
                resolve(JSON.parse(this.response))
            } else if (this.status === 404) {
                // 不能直接抛出 new HttpError('用户不存在'),因为网络请求是异步的
                reject(new HttpError('用户不存在'))
            } else {
                reject('加载失败')
            }
        }
        xhr.onerror = function () {
            reject(this)
        }
    })
} 
    <script src="./JS/ajax.js"></script>
    <script>
        let url = `http://localhost:8080/php`
        ajax(`${url}/user.php?name=后盾人`).then(
            user => ajax(`${url}/houdunren.php?id=${user.id}`)
        ).then(lessons => console.log(lessons)).catch(err => {
            console.log('err', err);
            if(err instanceof ParamError) {
                console.log(err.message);
            }
            if(err instanceof HttpError) {
                alert(err.message);
            }
        })
    </script>

finally

        const promise = new Promise((resolve, reject) => {
            reject('fail')
        })
            .then(msg => {
                console.log('resolve');
            })
            .catch(error => {
                console.log('error-catch', error);
            })
            .finally(() => {
                console.log('永远会执行');
            })

应用场景:loading加载效果,无论请求成功失败与否,最后加载都要结束

promise异步加载图片

        function loadImage(src) {
            return new Promise((resolve, reject) => {
                const image = new Image()
                image.src = src;
                image.onload = () => {
                    resolve(image)
                }
                image.onerror = reject;
                document.body.appendChild(image)
            })
        }
        loadImage('images/1.jpg').then(image => {
            image.style.border = 'solid 6px red'
        })

封装setTimeout定时器

        function timeout(delay = 1000) {
            return new Promise(resolve => setTimeout(resolve, delay))
        }
        timeout(2000)
            .then(() => {
                console.log('domgsin.com');
                return timeout(2000)
            })
            .then(value => {
                console.log('hdsci.com');
            })

构建扁平化的setInterval

        div {
            width: 100px;
            height: 100px;
            background-color: aqua;
            left: 0;
            position: absolute;
        }
        function interval(delay = 1000, cb) {
            return new Promise(resolve => {
                let id = setInterval(() => {
                    cb(id, resolve)
                }, delay);
            })
        }
        interval(100, (id, resolve) => {
            const div = document.querySelector('div')
            let left = parseInt(window.getComputedStyle(div).left)
            console.log(left);
            div.style.left = left + 10 + 'px'
            if (left >= 200) {
                clearInterval(id)
                resolve(div)
            }
        }).then(div => {
            console.log(div);
            return interval(100, (id, resolve) => {
                let width = parseInt(window.getComputedStyle(div).width)
                div.style.width = width - 10 + 'px'
                if (width <= 20) {
                    clearInterval(id)
                    resolve(div)
                }
            })
        }).then(div => {
            div.style.backgroundColor = 'red'
        })

script脚本的Promise加载引擎

        function loadScript(src) {
            return new Promise((resolve, reject) => {
                const script = document.createElement('script');
                script.src = src;
                script.onload = () => resolve(script);
                script.onerror = reject;
                document.body.appendChild(script);
            })
        }
        loadScript('JS/hd.js').then(script => {
            console.log(script);
            return loadScript('JS/houdunren.js')
        }).then(script => {
            console.log(script);
            houdunren()
        }).catch(err => {
            console.log(err);
        })

Promise.resolve

        function hd() { }
        /*
            函数也是对象
            可以往里面压属性
        */
        function query(name) {
            const cache = query.cache || (query.cache = new Map())
            if (cache.has(name)) {
                console.log('走缓存了');
                return Promise.resolve(cache.get(name))
            }
            return ajax(`http://localhost:8888/php/user.php?name=${name}`).then(user => {
                cache.set(name, user)
                console.log('没走缓存');
                return user
            })
        }
        query('后盾人').then(user => {
            console.log(user);
        })
        query('后盾人').then(user => {
            console.log(user);
        })
        setTimeout(() => {
            query('后盾人').then(user => {
                console.log(user);
            })
        }, 1000);
        /*
            Promise.resolve()
        */
        Promise.hd = function (value) {
            return new Promise((resolve, reject) => {
                resolve()
            })
        }
        console.log(Promise.hd('后端人'));
        // console.log(Promise.resolve('后盾人'));
        // Promise.resolve('后盾人').then(value => {
        //     console.log(value);
        // })

Promise.reject

        let hd = {
            then(resolve, rejecy) {
                resolve('学习')
            }
        }
        Promise.resolve(hd).then(value => {
            console.log(value);
        })
        Promise.reject('fail').then(value => {
            console.log(value);
        }).catch(err => {
            console.log(err);
        })
        new Promise((resolve, reject) => {
            resolve('bu成功')
        }).then(value => {
            console.log(value);
            if(value != '成功') {
                // throw new Error('fail')
                return Promise.reject('参数错误')
            }
        }).catch(err => {
            console.log(err);
        })
        // new Promise((resolve, reject) => {
        //     resolve('成功')
        // }).then(value => {
        //     console.log(value);
        // }).catch(err => {
        //     console.log(err);
        // })

Promise.all批量获取数据

    const p1 = new Promise((resolve, reject) => {
      // resolve('成功')
      setTimeout(() => {
        reject('fail')
      }, 1000);
    }).catch(err => {
      // catch解决状态
      console.log('p1', err);
    })
    const p2 = new Promise((resolve, reject) => {
      // resolve('成功2')
      setTimeout(() => {
        resolve('成功2')
      }, 1000);
    })
    Promise.all([p1, p2]).then(value => {
      console.log(value);
    }).catch(err => {
      // 如果p1, p2的失败状态没有做处理,可以在all处使用catch进行错误处理
      console.log('all', err);
    })
    // 应用场景
    function getUsers(names) {
      let promises =  names.map(name => {
        return ajax(`http://localhost:8888/php/user.php?name${name}`)
      })
      return Promise.all(promises)
    }
    getUsers(['后端人', '向军']).then(users => {
      console.log(users);
    })

Promise.allSettled

Promise.allSettled

Promise.allSettled() 方法返回一个在所有给定的promise都已经fulfilledrejected后的promise,并带有一个对象数组,每个对象表示对应的promise结果。

allSettled 用于处理多个promise ,只关注执行完成,不关注是否全部执行成功,allSettled 状态只会是fulfilled

    const p1 = new Promise((resolve, reject) => {
      resolve('resolved')
    })
    const p2 = new Promise((resolve, reject) => {
      // resolve('resolved')
      reject('fail')
    })
    /**
     * Promise.allSettled
     * 
    */
    Promise.allSettled([p1, p2]).then( results => {
      console.log(results);
    })

image.png

Promise.race

使用Promise.race() 处理容错异步,和race单词一样哪个Promise快用哪个,哪个先返回用哪个。

  • 以最快返回的promise为准
  • 如果最快返加的状态为rejected 那整个promiserejected执行cache
  • 如果参数不是promise,内部将自动转为promise
    const p1 = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('成功1')
      }, 3000);
    })
    const p2 = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('请求超时')
      }, 2000);
    })
    Promise.race([p1,p2]).then(res => {
      console.log(res);
    }).catch(err => {
      console.log(err);
    })

Promise队列

实现原理

如果 then 返回promise 时,后面的then 就是对返回的 promise 的处理

    let promise = Promise.resolve('好好学习')
    promise.then(res => {
      console.log(res);
      return new Promise((resolve) => {
        setTimeout(() => {
          console.log('1');
          resolve('成功2')
        }, 2000);
      })
    }).then(res1 => {
      console.log('res1', res1);
    })
    function queue(num) {
      let promise = Promise.resolve()
      num.map(v => {
        promise = promise.then(_ => {
          return v()
        })
      })
    }
    function p1() {
      return new Promise(resolve => {
        setTimeout(() => {
          console.log('p1');
          resolve()
        }, 1000);
      })
    }
    function p2() {
      return new Promise(resolve => {
        setTimeout(() => {
          console.log('p2');
          resolve()
        }, 1000);
      })
    }
    queue([p1, p2])

reduce封装Promise队列

    function queue(num) {
      num.reduce((promise, n) => {
        return promise.then(_ => {
          return new Promise(resolve => {
            console.log(n);
            setTimeout(() => {
              resolve()
            }, 1000);
          })
        })
      }, Promise.resolve())
    }
    queue([1, 2, 3, 4, 5, 6])

使用队列渲染数据

    class User {
      constructor() { }
      ajax(user) {
        let url = `http://localhost:8888/php/user.php?name=${user}`;
        return new Promise(resolve => {
          let xhr = new XMLHttpRequest();
          xhr.open("GET", url);
          xhr.send();
          xhr.onload = function () {
            if (this.status == 200) {
              resolve(JSON.parse(this.response));
            } else {
              reject(this);
            }
          };
        });
      }
      render(users) {
        users.reduce((promise, user) => {
          return promise.then(_ => {
            return ajax(user)
          }).then(user => {
            return this.view(user)
          })
        }, Promise.resolve())
      }
      view(user) {
        return new Promise(resovle => {
          console.log(user);
          let h2 = document.createElement('h2')
          h2.innerHTML = user.name;
          document.body.appendChild(h2)
          resovle()
        })
      }
    }
    new User().render(['houdunren', 'dongsu'])

async/await

使用 async/await 是promise 的语法糖,可以让编写 promise 更清晰易懂,也是推荐编写promise 的方式。

  • async/await 本质还是promise,只是更简洁的语法糖书写
  • async/await 使用更清晰的promise来替换 promise.then/catch 的方式
    class User {
            constructor(name){
                this.name = name
            }
            then(resolve, reject) {
                resolve()
            }
        }
        async function get() {
            await new User() // 这里执行了,才会继续往下走
            console.log('打印11111111');
        }
        get()
    class User {
            constructor(name){
                this.name = name
            }
            then(resolve, reject) {
                let user = ajax(`http://localhost:8888/php/user.php?name=${this.name}`)
                resolve()
            }
        }
        async function get() {
            let user = await new User('东酥') // 这里执行了,才会继续往下走
            console.log('打印11111111', user);
        }
        get()

async和await的几种声明方法

    // 方法一
        let hd = {
            async get(name) {
                return await ajax(`http://localhost:8888/php/user.php?name=${name}`)
            }
        }
        hd.get('houdunren').then(user => {
            console.log(user);
        })
        // 方法二
        async function get(name) {
            let user = await ajax(`http://localhost:8888/php/user.php?name=${name}`)
        }
        get('houdunren').then(user => {
            console.log(user);
        })
        // 方法三
        class User {
            async get(name) {
                return await ajax(`http://localhost:8888/php/user.php?name=${name}`)
            }
        }
        new User().get('houdunren').then(user => {
            console.log(user);
        })

async的错误处理

        async function hd () {
            // console.log(a);
            throw new Error('fail')
        }
        hd().catch(err => {
            console.log(err);
        })

后盾人-async

学习笔记