JavaScript Promise对象
事件循环
JavaScript是一门单线程的编程语言,所以没有并发并行等特性。
为了协调事件、用户交互、脚本、UI 渲染和网络处理等行为,防止主线程的不阻塞,(事件循环)
Event Loop的方案应用而生。
JavaScript处理任务是在等待任务、执行任务 、休眠等待新任务中不断循环中,也称这种机制为事件循环。
主线程中的任务执行完后,才执行任务队列中的任务
有新任务到来时会将其放入队列,采取先进先执行的策略执行队列中的任务
比如多个
setTimeout同时到时间了,就要依次执行
任务包括
script(整体代码)、
setTimeout、
setInterval、DOM渲染、DOM事件、
Promise、
XMLHTTPREQUEST等
任务详解
任务分类
任务大致分为以下三种:
主线程任务
应放入宏队列中的任务
应放入微队列中的任务
放入宏队列中的任务 | ||
---|---|---|
# | 浏览器 | 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对象都具有三种状态,分别是
pending,
fulfilled,
rejected。
当一个
Promise对象状态改变过后,将不能再次改变。
pending指初始等待状态,初始化promise时的状态resolve指已经解决,将promise状态设置为fulfilledreject指拒绝处理或未解决,将promise状态设置为rejected
当没有使用
resolve或
reject更改状态时,状态为
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对象状态为
resolve或
reject时,可以紧跟
then方法,该方法可接收两个个函数对象,用于处理
Promise对象
reject或
resolve传递过来的值。
<script> "use strict"; new Promise(function (resolve, reject) { reject("未解决"); }) .then(success => { console.log("resolve:", success); }, error => { console.log("reject:", error); // resolve: 未解决 } ); </script>
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
无论状态是
fulfilled或
rejected都会执行此动作,
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()方法可以快速的返回一个状态是
fulfilled的
Promise对象。
<script> "use strict"; Promise.resolve("成功").then(success=>console.log(success)); // 成功 </script>
reject
使用
Promise.reject()方法可以快速的返回一个状态是
rejected的
Promise对象。
<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那整个Promise为rejected执行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/await是
Promise的语法糖,可以让编写
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>
- JavaScript中对Promise对象的理解
- es6 javascript的Promise对象(上)
- JavaScript-Promise对象基础
- 通过一道笔试题浅谈javascript中的promise对象
- 【javascript笔记】Promise对象
- Javascript 在循环中使用Promise对象
- JavaScript(ES6) - Promise对象
- 【前端】JavaScript ES6 之Promise对象概要分析
- 【javascript笔记】Promise对象
- Javascript的Promise对象实践
- JavaScript Promise对象详解
- 浅谈Javascript中Promise对象的实现
- javascript使用Promise对象实现异步编程
- ES6新特性:Javascript中内置的延迟对象Promise
- 浅谈Javascript中Promise对象的实现
- 通过一道笔试题浅谈javascript中的promise对象
- es6 javascript的Promise对象(下)
- javascript使用Promise对象实现异步编程
- ES6新特性:JavaScript中内置的延迟对象Promise
- 初识Javascript Promise对象