AI智能
改变未来

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

状态设置为

fulfilled

  reject

指拒绝处理或未解决,将

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>
赞(0) 打赏
未经允许不得转载:爱站程序员基地 » JavaScript Promise对象