JavaScript Promise

事件循环

  JavaScript是一门单线程的编程语言,所以没有并发并行等特性。

  为了协调事件、用户交互、脚本、UI 渲染和网络处理等行为,防止主线程的不阻塞,(事件循环)Event Loop的方案应用而生。

  JavaScript处理任务是在等待任务、执行任务 、休眠等待新任务中不断循环中,也称这种机制为事件循环。

  主线程中的任务执行完后,才执行任务队列中的任务

  有新任务到来时会将其放入队列,采取先进先执行的策略执行队列中的任务

  比如多个 setTimeout 同时到时间了,就要依次执行

  任务包括 script(整体代码)、 setTimeoutsetInterval、DOM渲染、DOM事件、PromiseXMLHTTPREQUEST

image-20200813235308171

任务详解

任务分类

  任务大致分为以下三种:

  主线程任务

  应放入宏队列中的任务

  应放入微队列中的任务

放入宏队列中的任务  
# 浏览器 Node
setTimeout
setInterval
setImmediate x
requestAnimationFrame x
放入微队列中的任务  
# 浏览器 Node
process.nextTick x
MutationObserver x
Promise.then catch finally

执行顺序

  根据任务的不同,执行顺序也有所不同:

  1.主线程任务

  2.微队列任务

  3.宏队列任务

<script>

        "use strict";

        new Promise(resolve => {
                console.log("主线程任务执行 1...")
                resolve();
        }).then(_ => {
                console.log("微队列任务执行 7...");
        });

        console.log("主线程任务执行 2...");

        setTimeout(() => {
                console.log("宏队列任务执行 9...");
        }, 1);

        console.log("主线程任务执行 3...");

        new Promise(resolve => {
                console.log("主线程任务执行 4...")
                resolve();
        }).then(_ => {
                console.log("微队列任务执行 8...");
        });

        console.log("主线程任务执行 5...");
        console.log("主线程任务执行 6...");
/*
        
        主线程任务执行 1...
        主线程任务执行 2...
        主线程任务执行 3...
        主线程任务执行 4...
        主线程任务执行 5...
        主线程任务执行 6...
        微队列任务执行 7...
        微队列任务执行 8...
        宏队列任务执行 9...
        
        */

</script>

作用体现

  使用Promise能让代码变得更易阅读,方便后期维护。

  特别是在回调函数嵌套上,更应该使用Promise来书写代码。

嵌套问题

  以下示例将展示通过Js来使得<div>标签形态在不同时刻发生变化。

  代码逻辑虽然清晰但是定时器回调函数嵌套太过复杂,阅读体验较差。

<!DOCTYPE html>
<html lang="en">

<head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <style>
                div {
                        width: 100px;
                        height: 100px;
                        background-color: red;
                        transition: 1s;
                }

                button {

                        margin-top: 20px;
                }
        </style>
</head>

<body>
        <div></div>
        <button>点我</button>
</body>
<script>

        "use strict";

        document.querySelector("button").addEventListener("click", () => {

                let div = document.querySelector("div");
                div.style.backgroundColor = "blue";
                setTimeout(() => {
                        div.style.width = "50px";
                        setTimeout(() => {
                                div.style.transform = "translate(100px)";
                                setTimeout(() => {
                                        div.style.width = "100px";
                                        div.style.backgroundColor = "red";
                                        setTimeout(() => {
                                                div.style.backgroundColor = "yellow";
                                        },1000);

                                }, 1000);

                        }, 1000);

                }, 1000);
        });

</script>

</html>

尝试解决

  使用Promise来解决该问题。

  这里看不懂没关系,下面会慢慢进行剖析,只是感受一下是不是嵌套没那么严重了看起来好看多了。

<!DOCTYPE html>
<html lang="en">

<head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <style>
                div {
                        width: 100px;
                        height: 100px;
                        background-color: red;
                        transition: 1s;
                }

                button {

                        margin-top: 20px;
                }
        </style>
</head>

<body>
        <div></div>
        <button>点我</button>
</body>
<script>

        "use strict";

        function chain(callback, time=1000) {

                return new Promise(function (resolve, reject) {
                        setTimeout(() => {
                                let res = callback();
                                resolve(res);
                        }, time);
                });
        }

        document.querySelector("button").addEventListener("click", () => {
                new Promise(function (resolve, reject) {

                        let div = document.querySelector("div");
                        div.style.backgroundColor = "blue";
                        resolve(div);

                }).then(div => {

                        return chain(() => {
                                div.style.width = "50px";
                                return div;
                        });

                }).then(div => {


                        return chain(() => {
                                div.style.transform = "translate(100px)";
                                return div;
                        });


                }).then(div => {

                        return chain(() => {
                                div.style.width = "100px";
                                div.style.backgroundColor = "red";
                                return div;
                        })


                }).then(div => {

                        return chain(() => {
                                div.style.backgroundColor = "yellow";
                                return div;
                        })


                })
        });

</script>

Promise

  JavaScript 中存在很多异步操作,Promise 将异步操作队列化,按照期望的顺序执行,返回符合预期的结果。

  可以通过链式调用多个 Promise 达到我们的目的,如同上面示例一样会让代码可读性大幅度提升。

声明状态

  每一个Promise对象都接收一个函数,该函数需要提供两个参数,分别是resolve以及reject,代表当前函数中的任务成功与失败,这是属于线程任务的,所以会优先执行。

  此外,每一个Promise对象都具有三种状态,分别是pendingfulfilledrejected

  当一个Promise对象状态改变过后,将不能再次改变。

  pending 指初始等待状态,初始化 promise 时的状态

  resolve 指已经解决,将 promise 状态设置为fulfilled

  reject 指拒绝处理或未解决,将 promise 状态设置为rejected

image-20200814140550236

  当没有使用 resolvereject 更改状态时,状态为 pending

<script>
        "use strict";

        let p1 = new Promise(function (resolve, reject) { });
        console.log(p1);  // Promise {<pending>}

</script>

  使用resolve修改状态后,状态为fulfilled

<script>
        "use strict";

        let p1 = new Promise(function (resolve, reject) {
                resolve("已解决");
        });
        console.log(p1);  // Promise {<fulfilled>: "已解决"}

</script>

  使用reject修改状态后,状态为rejected

<script>
        "use strict";

        let p1 = new Promise(function (resolve, reject) {
                reject("未解决");
        });
        console.log(p1);  // Promise {<rejected>: "未解决"}

</script>

then

  在一个Promise对象状态为resolvereject时,可以紧跟then方法,该方法可接收两个个函数对象,用于处理Promise对象rejectresolve传递过来的值。

<script>

        "use strict";

        new Promise(function (resolve, reject) {
                reject("未解决");
        })
                .then(success => {
                        console.log("resolve:", success);  
                },
                        error => {
                                console.log("reject:", error);  // resolve: 未解决
                        }
                );

</script>

image-20200814144134696

catch

  每个then都可以指定第二个函数用于处理上一个Promise失败的情况,如果每个then都进行这样设置会显得很麻烦,所以我们只需要使用catch即可。

  catch 可以捕获之前所有 promise 的错误,所以建议将 catch 放在最后。

  建议使用 catch 处理错误

  将 catch 放在最后面用于统一处理前面发生的错误

  错误是冒泡操作的,下面没有任何一个then 定义第二个函数,将一直冒泡到 catch 处理错误

<script>

        "use strict";

        new Promise((resolve, reject) => {
                reject("失败");
        }).then(success => {
                console.log("成功");
        }).then(success => {
                console.log("成功");
        }).catch(error => {
                console.log(error);  // 失败
        })

</script>

  catch也可捕捉到throw自动触发的异常。

<script>

        "use strict";

        new Promise((resolve, reject) => {
                resolve("成功");
        }).then(success => {
                throw new Error("失败");
        }).catch(error=>{
                console.log(error);  // Error: 失败
        })

</script>

finally

  无论状态是fulfilledrejected都会执行此动作,finally 与状态无关。

<script>

        "use strict";

        new Promise((resolve, reject) => {
                reject("失败");
        }).then(success => {
                console.log("成功");
        }).catch(error => {
                console.log(error);  // 失败
        }).finally(() => {
                console.log("都会执行"); // 都会执行
        })

</script>

链式调用

  使用Promise进行链式调用,可以规避掉嵌套问题。

基本概念

  其实每一个then都是一个新的Promise,默认返回为fulfilled状态。

<script>

        "use strict";
        
        let p1 = new Promise(function (resolve, reject) {
                resolve("已解决");
        })
        let p2 = p1.then(success => {
                console.log(success);
        }, error => {
                console.log(error);
        });

        setTimeout(() => {
                console.log(p2);  // 宏任务队列中的任务最后执行  Promise {<fulfilled>: undefined}
        },3000)

</script>

  此时就会产生一种链式关系,每一个then都是一个新的Promise对象,而每个then的作用又都是处理上个Promise对象的状态。

  要想使用链式调用,一定要搞明白每一个then的返回值。

  返回了一个值,那么 then 返回的 Promise将会成为接受状态,并且将返回的值作为接受状态的回调函数的参数值。

  没有返回任何值,那么 then 返回的 Promise将会成为接受状态,并且该接受状态的回调函数的参数值为 undefined

  抛出一个错误,那么 then 返回的 Promise将会成为拒绝状态,并且将抛出的错误作为拒绝状态的回调函数的参数值。

  返回一个已经是接受状态的 Promise,那么 then 返回的 Promise也会成为接受状态,并且将那个 Promise的接受状态的回调函数的参数值作为该被返回的Promise的接受状态回调函数的参数值。

  返回一个已经是拒绝状态的 Promise,那么 then 返回的 Promise也会成为拒绝状态,并且将那个 Promise的拒绝状态的回调函数的参数值作为该被返回的Promise的拒绝状态回调函数的参数值。

  返回一个未定状态(pending)的 Promise,那么 then 返回 Promise 的状态也是未定的,并且它的终态与那个 Promise 的终态相同;同时,它变为终态时调用的回调函数参数与那个 Promise 变为终态时的回调函数的参数是相同的。

无返回

  上一个then无返回值时该then创建的Promise对象为fulfilled状态。

  下一个then会立即执行,接收值为undefined

<script>

        "use strict";

        new Promise((resolve, reject) => {
                resolve("成功");
        }).then(success => {
                console.log("无返回1"); // 上一个Promise状态是fulfilled 立刻执行
        }).then(success => {
                console.log("无返回2"); // 上一个Promise状态是fulfilled 立刻执行
        })

</script>

返回值

  上一个then有返回值时该then创建的Promise对象为fulfilled状态。

  下一个then会立即执行,接收值为上一个then的返回值。

<script>

        "use strict";

        new Promise((resolve, reject) => {
                resolve("成功");
        }).then(success => {
                return "v1"  // 上一个Promise状态是fulfilled 立刻执行
        }).then(success => {
                console.log(success);  // v1 上一个Promise状态是fulfilled 立刻执行
        })

</script>

返回Promise

  上一个then有返回值且该返回值是一个Promise对象的话下一个then会等待该Promise对象状态改变后再进行执行,接收值根据被返回的Promise对象的任务处理状态来决定。

<script>

        "use strict";

        new Promise((resolve, reject) => {
                resolve("成功");
        }).then(success => {
                return new Promise((resolve, reject) => {
                        // resolve("成功");  
                })
        }).then(success => {
                console.log(success);  //  上一个Promise状态是pending 不执行,等待状态变化
        })

</script>

嵌套解决

  我们可以利用在一个then中返回Promise下面的then会等待状态的特性,对定时器回调函数嵌套进行优化。

<!DOCTYPE html>
<html lang="en">

<head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <style>
                div {
                        width: 100px;
                        height: 100px;
                        background-color: red;
                        transition: 1s;
                }

                button {

                        margin-top: 20px;
                }
        </style>
</head>

<body>
        <div></div>
        <button>点我</button>
</body>
<script>

        "use strict";

        document.querySelector("button").addEventListener("click", () => {
                new Promise(function (resolve, reject) {

                        let div = document.querySelector("div");
                        div.style.backgroundColor = "blue";
                        resolve(div);

                }).then(div => {

                        return new Promise(function (resolve, reject) {

                                setTimeout(() => {
                                        div.style.width = "50px";
                                        resolve(div);
                                }, 1000);
                        })
               
                }).then(div => {
                 

                        return new Promise(function (resolve, reject) {
                                setTimeout(() => {
                                        div.style.transform = "translate(100px)";
                                        resolve(div);
                                }, 1000);
                        })


                }).then(div => {
                        return new Promise(function (resolve, reject) {
                                setTimeout(() => {
                                        div.style.width = "100px";
                                        div.style.backgroundColor = "red";
                                        resolve(div);
                                }, 1000);
                        })

                }).then(div => {
                        return new Promise(function (resolve, reject) {
                                setTimeout(() => {
                                        div.style.backgroundColor = "yellow";
                                        resolve(div);
                                }, 1000);
                        })
                })
        });

</script>

代码优化

  继续对上面的代码做优化。

<!DOCTYPE html>
<html lang="en">

<head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <style>
                div {
                        width: 100px;
                        height: 100px;
                        background-color: red;
                        transition: 1s;
                }

                button {

                        margin-top: 20px;
                }
        </style>
</head>

<body>
        <div></div>
        <button>点我</button>
</body>
<script>

        "use strict";

        function chain(callback, time=1000) {

                return new Promise(function (resolve, reject) {
                        setTimeout(() => {
                                let res = callback();
                                resolve(res);
                        }, time);
                });
        }

        document.querySelector("button").addEventListener("click", () => {
                new Promise(function (resolve, reject) {

                        let div = document.querySelector("div");
                        div.style.backgroundColor = "blue";
                        resolve(div);

                }).then(div => {

                        return chain(() => {
                                div.style.width = "50px";
                                return div;
                        });

                }).then(div => {


                        return chain(() => {
                                div.style.transform = "translate(100px)";
                                return div;
                        });


                }).then(div => {

                        return chain(() => {
                                div.style.width = "100px";
                                div.style.backgroundColor = "red";
                                return div;
                        })


                }).then(div => {

                        return chain(() => {
                                div.style.width = "100px";
                                div.style.backgroundColor = "yellow";
                                return div;
                        })


                })
        });

</script>

扩展接口

resolve

  使用 Promise.resolve() 方法可以快速的返回一个状态是fulfilledPromise对象。

<script>

        "use strict";

        Promise.resolve("成功").then(success=>console.log(success)); // 成功
        
</script>

reject

  使用 Promise.reject() 方法可以快速的返回一个状态是rejectedPromise对象。

<script>

        "use strict";

        Promise.reject("失败").then(null,error=>console.log(error)); // 失败
        // 使用null来对成功的处理进行占位

</script>

all

  使用Promise.all() 方法可以同时执行多个并行异步操作,比如页面加载时同进获取课程列表与推荐课程。

  任何一个 Promise 执行失败就会调用 catch方法

  适用于一次发送多个异步操作

  参数必须是可迭代类型,如Array/Set

  成功后返回 Promise 结果的有序数组

  以下示例将展示同时提交两个异步操作,只有当全部成功时才会执行Promise.all()其下的then

<script>

        "use strict";

        const p1 = new Promise((resolve, reject) => {
                setTimeout(() => {
                        resolve("成功");

                }, 3000);
        });

        const p2 = new Promise((resolve, reject) => {
                setTimeout(() => {
                        resolve("成功");

                }, 3000);
        });

        Promise.all([p1, p2])
                .then(success => {
                        console.log(success);  // (2) ["成功", "成功"]
                })
                .catch(error => {
                        console.log(error);  // 任何一个失败都会执行这里
                });


</script>

allSettled

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

<script>

        "use strict";

        const p1 = new Promise((resolve, reject) => {
                setTimeout(() => {
                        resolve("成功");

                }, 1000);
        });

        const p2 = new Promise((resolve, reject) => {
                setTimeout(() => {
                        resolve("成功");

                }, 3000);
        });

        Promise.allSettled([p1, p2])
                .then(success => {
                        console.log(success);
                })
        /*
        
        [{status: "fulfilled", value: "成功"}, {status: "fulfilled", value: "成功"}]
        
        */


</script>

race

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

  其实这在某些资源引用上比较常用,可以添加多个资源地址进行请求,谁先快就用谁的。

  以最快返回的Promise为准

  如果最快返加的状态为rejected 那整个Promiserejected执行cache

  如果参数不是Promise,内部将自动转为Promise

  下面示例中成功1比较快,就用成功1的。

<script>

        "use strict";

        const p1 = new Promise((resolve, reject) => {
                setTimeout(() => {
                        resolve("成功1");

                }, 1000);
        });

        const p2 = new Promise((resolve, reject) => {
                setTimeout(() => {
                        resolve("成功2");

                }, 3000);
        });

        Promise.race([p1, p2])
                .then(success => {
                        console.log(success);  // 成功1
                })
 
</script>

async/await

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

  async/await 本质还是Promise,只是更简洁的语法糖书写

async

  在某一个函数前加上async,该函数会返回一个Promise对象。

  我们可以依照标准Promise来操纵该对象。

<script>

        "use strict";

        async function get() {
                return "请求成功...";
        }

        get().then(success => {
                console.log(success);  // 请求成功...
        })

</script>

await

  使用 await 关键词后会等待Promise完。

  await 后面一般是Promise,如果不是直接返回

  await 必须放在 async定义的函数中使用

  await 用于替代 then 使编码更优雅

<script>

        "use strict";

        async function get() {
                const ajax = new Promise((resolve, reject) => {
                        setTimeout(()=>{
                                resolve("返回的结果");
                        },3000);
                });

                let result = await ajax;
                console.log(result);  // 返回的结果
        }

        get();

</script>

  一般await后面是外部其它的Promise对象

<script>

        "use strict";

        async function getName() {
                return new Promise((resolve, reject) => {
                        resolve("姓名数据...");
                });
        }

        async function getGrades() {
                return new Promise((resolve, reject) => {
                        resolve("成绩数据...");
                });
        }

        async function run() {

                let nameSet = await getName();
                let gradesSet = await getGrades();

                console.log(nameSet);
                console.log(gradesSet);

        }

        run();

</script>

异常处理

  Promise状态为rejected其实我们就可以将它归为出现异常了。

  当一个await发生异常时,其他的await不会进行执行。

<script>

        "use strict";

        async function getName() {
                return new Promise((resolve, reject) => {
                        reject("姓名数据获取失败...");
                })

        }


        async function getGrades() {
                return new Promise((resolve, reject) => {
                        resolve("成绩数据...");
                });
        }

        async function run() {
                let nameSet = await getName();  // Uncaught (in promise) 姓名数据获取失败...
                let gradesSet = await getGrades(); // 不执行
        }

        run();

</script>

  如果在async中不确定会不会抛出异常,我们可以在接收时使用catch进行处理。

<script>

        "use strict";

        async function getName() {
                return new Promise((resolve, reject) => {
                        reject("姓名数据获取失败...");
                })
                
        }

        async function run() {
                let nameSet = await getName().catch(error => console.log(error));
        }

        run();

</script>

  更推荐写成下面这种形式

<script>

        "use strict";

        async function getName() {
                return new Promise((resolve, reject) => {
                        reject("姓名数据获取失败...");
                })
                .catch(error => console.log(error));
        }

        async function run() {
                let nameSet = await getName();

        }

        run();

</script>

  也可使用try...catch进行处理。

<script>

        "use strict";

        async function getName() {
                return new Promise((resolve, reject) => {
                        reject("姓名数据获取失败...");
                });
        }

        async function run() {
                try {
                        let nameSet = await getName();
                } catch (e) {
                        console.log(e);  // 姓名数据获取失败...
                }
        }

        run();

</script>
posted @ 2020-08-14 19:14  云崖先生  阅读(300)  评论(0编辑  收藏  举报