我们了解Promise之前先了解一下什么是异步,因为Promise是用来处理异步操作的
一、什么是异步
异步(Asynchronous, async)是与同步(Synchronous, sync)相对的概念。总所周知,JavaScript 的代码执行的时候是跑在单线程上的,代码按照出现的顺序,从上到下一行一行的执行,也就是我们说的同步(同步不意味着所有步骤同时运行,而是指步骤在一个控制流序列中按顺序执行)。而异步的概念则是不保证同步的概念,也就是说,一个异步过程的执行将不再与原有的序列有顺序关系。
简单来理解就是:同步按你的代码顺序执行,异步不按照代码顺序执行,异步的执行效率更高。
实际开发中,某些业务的结果我们是不能同步获取的,而等待的结果也是不确定的。比如异步操作 ajax 获取数据,遍历一个大型的数组(同步操作),还有动态加载脚本文件然后初始化相关业务。
function loadScript(src) {
let script = document.createElement("script");
script.src = src;
document.head.append(script);
}
loadScript("./js/script.js");
init(); // 定义在 ./js/script.js 里的函数
这样的代码肯定是达不到效果的。因为加载脚本是需要花时间的,是一个异步的行为,浏览器执行 JavaScript 的时候并不会等到脚本加载完成的时候再去调用 init 函数。
远古时代的做法是使用回调函数,给处理函数传递一个回调函数来处理回调结果。
function loadScript(src, success, fail) {
let script = document.createElement("script");
script.src = src;
script.onload = success;
script.onerror = fail;
document.head.append(script);
}
loadScript("./js/script.js", success, fail);
function success() {
console.log("success");
init(); // 定义在 ./js/script.js 中的函数
}
function fail() {
console.log("fail");
}
试想一下,如果 success 函数又需要异步处理回调呢?OMG,那简直就是灾难,传说中的"回调地狱"青面獠牙向我们走来,它是恶魔,即使是聪明绝顶的程序猿都避而远之。
为了避免"回调地狱"吞噬程序猿茂密的毛发,Promise
解决方案就应运而生了。
二、Promise
Promise,是用来处理异步操作的,可以让我们写异步调用的时候写起来更加优雅,更加美观,人称“护发金刚”。顾名思义为承诺、许诺的意思,意思是使用了 Promise 之后他肯定会给我们答复,无论成功或者失败都会给我们一个答复。
我们可以把异步操作交给 Promise 来处理,什么时候处理好他通知我们,如果还有异步操作再交给 Promise 处理,这样就可以将异步的操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise 对象提供统一的接口,使得控制异步操作更加容易。
俗话说,神仙也有缺点。当然 Promise 也不例外。首先,无法取消 Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部。第三,当处于 Pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
纵使她有这些缺点,但丝毫影响我们对他的欢喜,甚至有些猿友对她爱得死去活来的。
Promise 首先是一个对象,它通常用于描述现在开始执行,一段时间后才能获得结果的行为(异步行为),内部保存了该异步行为的结果。然后,它还是一个有状态的对象:
- pending:待定
- fulfilled:兑现,有时候也叫解决(resolved)
- rejected:拒绝
一个 Promise 只有这 3 种状态,且状态的转换过程有且仅有 2 种:
- pending 到 fulfilled
- pending 到 rejected
三个属性,两个技能,她就是"回调地狱"的克星,程序猿的知音啊。
创建 Promise
调用 Promise 构造函数来创建一个 Promise。
let promise = new Promise((resolve, reject) => {});
Promise 构造函数接收一个函数作为参数,该函数的两个参数是 resolve,reject,它们由 JavaScript 引擎提供。
其中 resolve 函数的作用是当 Promise 对象转移到成功,调用 resolve 并将操作结果作为其参数传递出去;reject 函数的作用是当 Promise 对象的状态变为失败时,将操作报出的错误作为其参数传递出去。
由 new Promise 构造器返回的 Promise 对象具有如下内部属性:
- PromiseState:最初是 pending,resolve 被调用的时候变为 fulfilled,或者 reject 被调用时会变为 rejected。
- PromiseResult:最初是 undefined,resolve(value) 被调用时变为 value,或者在 reject(error) 被调用时变为 error。
三、Promise 常用方法
Promise.prototype.then()
调用 then 可以为实例注册两种状态的回调函数,当实例的状态为 fulfilled,会触发第一个函数执行,当实例的状态为 rejected,则触发第二个函数执行。
- onResolved:状态由 pending 转换成 fulfilled 时执行。
- onRejected:状态由 pending 转换成 rejected 时执行。
function onResolved(res) {
console.log("resolved" + res); // resolved3
}
function onRejected(err) {
console.log("rejected" + err);
}
new Promise((resolve, reject) => {
resolve(5);
}).then(onResolved, onRejected);
Promise.prototype.catch()
catch 只接受一个参数,也就是 rejected 抛出的值,一般用于异常处理。传统的try/catch捕获不了Promise内部的异常的,因为抛出异常这个动作是异步的。
在处理异常的时候,我们可以在catch中进行异常的捕获,也可以直接抛出异常。
function onRejected(err) {}
new Promise((resolve, reject) => {
reject();
}).catch(onRejected);
Promise.prototype.finally()
finally()方法只有当状态变化的时候才会执行,可以用来做一些程序的收尾工作,比如操作文件的时候关闭文件流。
function onFinally() {
console.log(12345); // 并不会执行
}
new Promise((resolve, reject) => {}).finally(onFinally);
all()
Promise 的 all 方法提供了并行执行异步操作的能力,在 all 中所有异步操作结束后才执行回调。
function p1() {
var promise1 = new Promise(function (resolve, reject) {
console.log("p1的第一条输出语句");
resolve("p1完成");
});
return promise1;
}
function p2() {
var promise2 = new Promise(function (resolve, reject) {
console.log("p2的第一条输出语句");
setTimeout(() => {
console.log("p2的第二条输出语句");
resolve("p2完成");
}, 2000);
});
return promise2;
}
function p3() {
var promise3 = new Promise(function (resolve, reject) {
console.log("p3的第一条输出语句");
resolve("p3完成");
});
return promise3;
}
Promise.all([p1(), p2(), p3()]).then(function (data) {
console.log(data);
});
输出结果:
p1的第一条输出语句;
p2的第一条输出语句;
p3的第一条输出语句;
p2的第二条输出语句[("p1完成", "p2完成", "p3完成")];
race()
在all中的回调函数中,等到所有的Promise都执行完,再来执行回调函数,race则不同它等到第一个Promise改变状态就开始执行回调函数。将上面的all
改为race
。
function p1(){
var promise1 = new Promise(function(resolve,reject){
console.log("p1的第一条输出语句");
resolve("p1完成");
})
return promise1;
}
function p2(){
var promise2 = new Promise(function(resolve,reject){
console.log("p2的第一条输出语句");
setTimeout(() => {
resolve("p2完成");
}, 2000);
})
return promise2;
}
function p3(){
var promise3 = new Promise(function(resolve,reject){
console.log("p3的第一条输出语句");
resolve("p3完成")
});
return promise3;
}
Promise.race([p1(),p2(),p3()]).then(function(data){
console.log(data);
})
结果:
p1的第一条输出语句
p2的第一条输出语句
p3的第一条输出语句
p1完成
p1完成返回后就不等p2,p3的返回了。
四、练习题
1、下面的代码输出什么?
new Promise((resolve, reject) => {
console.log('A')
resolve(3)
console.log('B')
}).then(res => {
console.log('C')
})
console.log('D')
// 打印结果:A B D C
上面这串代码的输出顺序是:A B D C。
解答
我们知道,立即执行函数会在 new Promise 调用的时候同步执行。所以为先打印出AB,然后遇到resolve(3)
时onResolved
函数会被推入微任务队列,然后就打印D,此时所有同步任务执行完成,浏览器会去检查微任务队列,发现存在一个,所以最后会去调用 onResolved 函数,打印出 C。
注意,执行 resolve()/reject()/finally()时都是异步的。
2、下面的代码输出什么?
new Promise((resolve, reject) => {
resolve(1)
}).then(res => {
console.log('A')
}).finally(() => {
console.log('B')
})
new Promise((resolve, reject) => {
resolve(2)
}).then(res => {
console.log('C')
}).finally(() => {
console.log('D')
})
// 打印结果:A C B D
上面这串代码的输出顺序是:A C B D。
解答
- 执行 resolve(1),将处理程序 A 推入微任务队列 1;
- 执行 resolve(2),将处理程序 C 推入微任务队列 2;
同步任务执行完成,执行微任务队列 1 里的内容,打印 A,A 所在函数执行完成后生成了一个 fulfilled 的新实例,由于新实例状态变化,所以会立即执行 finally() 处理程序 B 推入微任务队列 3; - 执行微任务队列 2 的内容,打印 C,C 所在函数执行完成后,同上条原理会将处理程序 D 推入微任务队列 4;
- 执行微任务队列 3 的内容,打印 B;
- 执行微任务队列 4 的内容,打印 D;
- 代码全部执行完成,最终打印:A C B D。
标签:function,异步,resolve,console,log,JavaScript,详解,Promise From: https://www.cnblogs.com/vant-xie/p/16755784.html