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>

浙公网安备 33010602011771号