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>