当前位置:网站首页>硬核解析Promise对象(这七个必会的常用API和七个关键问题你都了解吗?)
硬核解析Promise对象(这七个必会的常用API和七个关键问题你都了解吗?)
2022-04-23 18:11:00 【小杰学前端】
目录
Promise 构造函数: Promise (excutor) {}
一、Promise的理解与使用
Promise的概念
Promise 是异步编程的一种解决方案,而我们之前的解决方案是啥呢?没错,就是回调函数。Promise要比它更合理和更强大。
这样解释还不够清楚明白,那Promise到底是个啥呢
简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。
通俗讲,Promise翻译过来就是承诺,它是对未来事情的承诺,承诺不一定能完成(就像你答应你女朋友未来会给她买包,只是想先应付一下,以后买不买不一定呢),但是无论是否能完成都会有一个结果。
Promise的使用
Promise 是异步编程的一种解决方案,主要用来解决回调地狱的问题,可以有效减少回调嵌套。
从语法上说:Promise就是一个构造函数
从功能上说:Promise对象用来封装一个异步操作并可以获取其成功/失败的返回值
首先我们先想一下都有哪些是异步操作
fs文件操作 数据库操作 ajax请求操作 定时器
上面都是异步操作
fs文件操作 require('fs').readFile('./xiaojie.txt',(err,data)=>{})
数据库操作 $.get('/server',(data)=>{})
定时器 setTimeout(() => {}, 2000);
在promise前,我们进行异步操作时,用的都是纯回调函数的方式来进行的,那我们为什么使用promise呢,使用回调函数不行吗
注意,这是最重要的,面试必问
因为Promise支持链式调用,可以解决回调地狱问题
回调地狱问题
那么什么是回调地狱呢 为啥要解决他呢 Promise又是怎么解决的呢
下面我们一个一个来解决:
首先我们看下面这段代码,我们想分三次输出数字,每次间隔一秒钟,那我们肯定得这么做:
setTimeout(function () {
console.log(111);
setTimeout(function () {
console.log(222);
setTimeout(function () {
console.log(333);
}, 1000);
}, 1000);
}, 1000);
代码嵌套,在一个复杂的程序当中,是非常不便于阅读的并且不便于异常处理,因为在每一层我们都需要对错误进行异常处理,可能会写很多重复的代码而且会让缩进格式变得非常冗赘,当嵌套层数过多,会出现“回调地狱”。在这里就体现出用一种新的方式书写代码有多重要。用promise方法去处理这类问题,无疑会更合适。
解决方案
promise链式调用
new Promise((resolve, reject) => {
setTimeout(() => {
console.log(111);
resolve();
}, 1000);
}).then(() => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(222);
resolve();
}, 1000);
});
}).then(() => {
setTimeout(() =>{
console.log(333);
}, 1000);
});
这样最后的输出效果是相同的,而且会把嵌套的代码语句书写改变为按序书写,但不改变其执行的异步过程,这正是promise方法的作用及优点。
Promise的基本用法
ES6 规定,Promise 是一个构造函数,即然是构造函数那他就可以进行对象实例化。
promise在对象实例化的时候需要接受参数,这个参数是一个函数类型的值
该函数的两个参数分别是 resolve 和 reject 。
resolve是解决的意思,reject是拒绝的意思。
这两个都是函数类型的数据
当异步任务成功时调用resolve,当异步任务失败时调用reject
下面代码创造了一个Promise实例。
const p = new Promise((resolve, reject) => {
// ... some code
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
resolve() 在调用完后,可以将promise对象(p)的状态设置为成功
reject() 相反,调用完后,可以将promise对象(p)的状态设置为失败
我们创建完promise实例后需要调用then方法,他在执行时需要接收两个参数,而且两个参数都是函数类型的值
p.then((value) => {},(error) => {});
第一个参数是对象成功时的回调,第二个参数是对象失败时的回调。
这两个参数都是可选的,不一定要提供。它们都接受 Promise 对象传出的值作为参数。
通俗的说,如果promise对象状态成功就执行第一个回调函数,如果状态失败就执行第二个回调函数
Promise实践初体验
我们先不展开讲解Promise的各种API和原理,先做两个实践应用,简单体验一下Promise。
fs模块读取文件
这个需要我们安装node.js环境,fs模块的作用就是可以对计算机的硬盘进行读写操作
我们的案例要求是这样的:
读取当前目录下的nba.txt文件里的内容:
传统回调函数形式:
//引入fs模块
const fs = require('fs');
fs.readFile('./nba.txt', (err, data) => {
// 如果出错 则抛出错误
if(err) throw err;
//没有出错就输出文件内容
console.log(data.toString());
});
PS D:\用户目录\Desktop\home\html (2)\4.20> node zyj.js
勇士总冠军
这样的话我们的nba.txt里的内容就输出出来了
Promise封装形式:
//封装一个函数 mineReadFile 读取文件内容
//参数: path 文件路径
//返回: promise 对象
function mineReadFile(path){
return new Promise((resolve, reject) => {
//读取文件
require('fs').readFile(path, (err, data) =>{
//判断
if(err) reject(err);
//成功
resolve(data);
});
});
}
mineReadFile('./nba.txt')
.then(value=>{
//输出文件内容
console.log(value.toString());
}, error=>{
console.log(error);
});
如果成功的话data就是一个buffer需要tostring方法把它转化为字符串,如果出错就把错误的对象打印出来
当我们输出正常时:
PS D:\用户目录\Desktop\home\html (2)\4.20> node zyj.js
勇士总冠军
我们故意把路径打错,看看错误会输出什么:
PS D:\用户目录\Desktop\home\html (2)\4.20> node zyj.js
[Error: ENOENT: no such file or directory, open 'D:\用户目录\Desktop\home\html (2)\4.20\na.txt'] {
errno: -4058,
code: 'ENOENT',
syscall: 'open',
path: 'D:\\用户目录\\Desktop\\home\\html (2)\\4.20\\na.txt'
}
输出的就是error这个错误对象里的内容
使用 promise 封装 ajax 异步请求
<script >
/*可复用的发 ajax 请求的函数: xhr + promise*/
function promiseAjax(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open("GET", url);
xhr.send();
xhr.onreadystatechange = () => {
if (xhr.readyState !== 4) return;
// 请求成功, 调用 resolve(value)
if (xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.response);
} else { // 请求失败, 调用 reject(reason)
reject(xhr.status);
}
}
})
}
promiseAjax('http://www.liulongbin.top:3006/api/getbooks').then(data => {
console.log('显示成功数据', data)
},
error => {
alert(error)
})
</script>
输出结果:
当我们故意把地址打错:
promise对象状态属性
Promise 对象的状态不受外界影响。Promise 对象有三种状态:
pending (进行中)、fulfilled (已成功) 和 rejected (已失败)。
一旦状态改变,就不会再变,任何时候都可以得到这个结果。
Promise对象的状态改变,只有两种可能:从 pending 变为 fulfilled(resolved)和从 pending 变为 rejected。
只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。
只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。
如果改变已经发生了,你再对
Promise
对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
promise对象结果值属性
这是 promise 实例对象中的另一个属性 [promiseResult] ,保存着异步任务 [成功/失败] 的结果
resolve和reject两个方法是可以对实例对象的结果属性进行修改,除了他俩谁也改不了
小案例
在对Promise有个大概了解后,我们看一下下面的代码:
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms, 'done');
});
}
timeout(100).then((value) => {
console.log(value);
});
分析:
上面代码中,timeout 方法返回一个Promise实例对象,表示一段时间以后才会发生的结果。过了指定的时间(ms参数)以后, 计时器会调用 resolve(),并且把结果值(done)传进来,在调用完后,可以将promise对象(p)的状态设置为成功 ,他的状态改变了就会触发then方法绑定的回调函数,把传进来的结果值(done)输出出来。
二、Promise API
Promise 构造函数: Promise (excutor) {}
(1) executor 函数: 执行器 (resolve, reject) => {}
(2) resolve 函数: 内部定义成功时我们调用的函数 value => {}
(3) reject 函数: 内部定义失败时我们调用的函数 reason => {}
重点:executor 会在 Promise 内部立即同步调用
我们来看下面这段代码
let p = new Promise(function(resolve, reject) {
console.log('jack');
resolve();
});
p.then(value=> {
console.log('andy');
});
console.log('Hi!');
// jack
// Hi!
// andy
上面代码中,Promise 新建后立即执行,所以首先输出的是 'jack'。然后,then
方法指定的回调函数,将在当前脚本所有同步任务执行完才会执行,所以再输出 'Hi','andy' 最后输出。
实际上,这个运行结果相关知识点是 [ 宏任务与微任务 ] ,单独梳理在下方:
- 宏任务: setTimeout , setInterval , Ajax , DOM事件
- 微任务: Promise async/await
- 微任务执行时机比宏任务要早(先记住)
下面我们看一道面试题:
console.log(100);
setTimeout(()=>{
console.log(200);
})
setTimeout(()=>{
console.log(201);
})
Promise.resolve().then(()=>{
console.log(300);
})
console.log(400);
// 100 400 300 200 201
大家都做对了吗,如果没有做对的话,再结合一下JS的运行机制就可以轻松理解了:
- 所有的同步任务都在主线程上执行,行成一个执行栈。
- 除了主线程之外,还存在一个任务列队,只要异步任务有了运行结果,就在任务列队中植入一个时间标记。
- 主线程完成所有任务(执行栈清空),就会读取任务列队,先执行微任务队列在执行宏任务队列。
- 重复上面三步。
Promise.prototype.then()
then方法是定义在原型对象Promise.prototype上的。它的作用是为 Promise 实例添加状态改变时的回调函数
then方法方法返回的是一个新的Promise实例
then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。
let p = new Promise((resolve, reject) => {
setTimeout(resolve, 1000, 'jack');
});
p.then(value => {
console.log(value);
return 'andy';
}).then(value => console.log(value));
//jack
//andy
then()方法会返回一个新的Promise对象, 所以then后面可以继续跟一个then方法进行链式调用, 如果then方法的回调函数有返回数据,那么这个数据会作为下一个then的回调函数的参数,这块知识点涉及到链式调用,在Promise关键问题第五个串联多个任务里有详细讲解。大家在这里先做一个简单了解。
采用链式的then, 会等待前一个Promise状态发生改变才会被调用
then的回调函数也可能会返回一个Promise对象,这个时候就会覆盖then自己返回的Promise对象,这时后一个回调函数,就会等待该Promise对象的状态发生变化,才会被调用。
let p = new Promise((resolve, reject) => {
setTimeout(resolve, 1000, 'jack');
});
p.then(value => {
console.log(value);
return new Promise((resolve,reject) => {
setTimeout(resolve,1000,'andy');
});
}).then(value => console.log(value));
//jack
//andy
上面代码中,第一个then方法指定的回调函数,返回的是另一个Promise对象。这时,第二个then方法指定的回调函数,就会等待这个新的Promise对象状态发生变化。
因为第二个回调函数有一个定时器,所以一秒钟后状态变为resolved,第二个then方法看到状态改变了才会去执行log输出语句
Promise.prototype.catch()
Promise.prototype.catch()方法是 .then(null, rejection) 或 .then(undefined, rejection) 的别名,用于指定发生错误时的回调函数。
let p = new Promise((resolve, reject) => {
reject('what is the matter');
});
p.catch(error => {
console.log(error);
})
//what is the matter
如果Promise 对象状态变为rejected,就会调用 catch() 方法指定的回调函数,处理这个错误。
let p = new Promise((resolve, reject) => {
throw new Error('抛出了错误')
});
p.catch(error => {
console.log(error);
})
//Error: 抛出了错误
上面代码中,Promise抛出一个错误,同样会被 catch() 方法指定的回调函数捕获。
值得注意的是:如果 Promise 状态已经变成 resolved ,再抛出错误是无效的。
let p = new Promise((resolve, reject) => {
resolve('success');
throw new Error('抛出了错误')
});
p.then(value => {
console.log(value);
}).catch(error => {
console.log(error);
})
//success
上面代码中,抛出错误在 resolve 语句的后面,那就不会被捕获,等于没有抛出。因为 Promise 的状态一旦改变,就永久保持该状态,不会再变了。
Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个 catch 语句捕获。
我们知道每个then方法都会产生一个新的Promise实例,如果一个Promise对象后面紧跟着多个then方法,它们之中任何一个抛出的错误,都会被最后一个catch()捕获。
重点:所以,不要在 then() 方法里面定义 Reject 状态的回调函数(即 then 的第二个参数),最 好使用catch方法,这样可以捕获前面then方法执行中的错误。
Promise.resolve()
有时需要将现有对象转为 Promise 对象,Promise.resolve() 方法就起到这个作用。
let p = Promise.resolve('jack');
console.log(p);
打开控制台,看看上面这段代码输出了什么:
他的结果是一个Promise对象,而且状态为成功,成功的值为我们传入的参数 'jack' 。
Promise.resolve() 等价于下面的写法:
Promise.resolve('jack')
// 等价于
new Promise(resolve => {resolve('jack')})
Promise.resolve() 方法的参数分成两种情况:
参数是一个Promise实例:
如果参数是 Promise 实例,那么Promise.resolve() 将不做任何修改、原封不动地返回这个实例。
let p1 = Promise.resolve('jack');
let p2 = Promise.resolve(p1);
console.log(p2);
//Promise {[[PromiseState]]: 'fulfilled', [[PromiseResult]]: 'jack'}
参数不是Promise对象或没有任何参数:
Promise.resolve() 方法返回一个新的 Promise 对象,状态为 resolved
const p = Promise.resolve();
p.then(function () {
// ...
});
上面代码的变量 p 就是一个 Promise 对象。
需要注意的是,立即 resolve() 的 Promise 对象,是在本轮“事件循环”(event loop)的结束时执行,而不是在下一轮“事件循环”的开始时。
setTimeout(function () {
console.log('jack');
}, 0);
Promise.resolve().then(function () {
console.log('andy');
});
console.log('david');
// david
// andy
// jack
上面代码中,setTimeout( fn , 0) 下一轮“事件循环”开始时执行,Promise.resolve() 在本轮“事件循环”结束时执行,console.log('david') 是立即执行,因此最先输出。
Promise.reject()
Promise.reject(reason) 方法也会返回一个新的 Promise 实例,该实例的状态为 rejected 。
let p1 = Promise.reject('出错啦');
console.log(p1);
//Promise { <rejected> '出错啦' }
const p = Promise.reject('出错啦');
// 等同于
const p = new Promise((resolve, reject) => reject('出错啦'))
p.then(null, function (s) {
console.log(s)
});
// 出错了
即便我们传入的参数是一个成功的Promise对象,他的结果也是失败:
let p1 = Promise.reject(new Promise((resolve, reject) => {
resolve('ok');
}));
console.log(p1);
//Promise { <rejected> Promise { 'ok' } }
此时我们的Promise对象p1的状态是失败,它失败的结果是我们传入的这个值,也就是这个成功的Promise对象,简单理解就是,不论怎样,Promise.reject()返回的状态一定是rejected
Promise.all()
Promise.all() 方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.all([p1, p2, p3]);
- Promise.all() 方法接受一个数组作为参数,
- p1, p2, p3 都是 Promise 实例,如果不是,就会先调用Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。
let p1 = Promise.resolve('jack');
let p2 = Promise.resolve(111);
const p3 = Promise.all([p1,p2,'andy']);
console.log(p3);
这里我们定义了两个状态为成功的Promise对象 p1和p2,我们想通过Promise.all() 方法将多个 Promise 实例,包装成一个新的 Promise 实例 p3,但是参数数组里的最后一个元素并不是Promise对象而是一个字符串,那么 p3 会输出什么呢?
输出结果如上图所示,就如我们前面提到的,如果数组内有元素不是Promise实例,会先调用Promise.resolve方法,将参数转为 Promise 实例,再进行处理。
p 的状态由 p1, p2, p3 决定,分成两种情况:
1. 只有 p1, p2, p3 的状态都变成 fulfilled ,p 的状态才会变成 fulfilled ,此时 p1, p2, p3 的返回值组成一个数组,传递给p的回调函数。
2. 只要 p1, p2, p3 之中有一个为 rejected,p 的状态就变成 rejected ,此时第一个被 reject 的实例的返回值,会传递给p的回调函数。
下面我们来分析一段代码:
const p1 = new Promise((resolve, reject) => {
resolve('success');
}).then(value => value).catch(reason => reason);
const p2 = new Promise((resolve, reject) => {
throw new Error('报错了');
}).then(value => value).catch(reason => reason);
Promise.all([p1, p2]).then(value => {console.log(value)})
.catch(reason => {console.log(reason)});
上面代码中,p1会resolved,p2首先会rejected,但是p2有自己的catch方法,该方法返回的是一个新的 Promise 实例,p2指向的实际上是这个实例。该实例执行完catch方法后,也会变成resolved,导致Promise.all() 方法参数里面的两个实例都会resolved,因此会调用then方法指定的回调函数,而不会调用catch方法指定的回调函数。
下面是这段代码的输出结果:
如果p2没有自己的catch方法,就会调用 Promise.all() 的 catch 方法,这个时候输出结果就是这样的:
Promise.race()
Promise.race() 方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.race([p1, p2, p3]);
上面代码中,只要p1 , p2 , p3 之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。
我们分析一下下面一段代码应该输出什么:
let p1 = new Promise((resolve,reject)=>{
setTimeout(() => {
resolve('ok');
}, 1000);
})
let p2 = Promise.resolve('success');
let p3 = Promise.resolve('oh year');
const result = Promise.race([p1,p2,p3]);
console.log(result);
我们先看一下p1,p2,p3他们三个谁先改变状态,如果p1先改变状态,那 result 结果就是由p1决定的,如果p1成功那么result就成功,它成功的结果就是result成功的结果。但是因为在我们的例子里p1是一个定时器,是一个异步任务,那肯定不是p1,顺着往下排,p1不行那就肯定p2先改变状态,我们在控制台看看输出的结果:
因为p2的状态是成功,所以result的状态就是fulfilled,因为p2结果是success,所以result结果也是result。
三、Promise关键问题
1.如何修改对象的状态
- resolve(value): 如果当前是 pending 就会变为 resolved
- reject(reason): 如果当前是 pending 就会变为 rejected
- 抛出异常: 如果当前是 pending 就会变为 rejected(可以抛一个Error实例对象)
2.能否执行多个回调
当 promise 改变为对应状态时都会调用
我们看下面这段代码:
let p = new Promise((resolve, reject) => { resolve('OK');});
p.then(value => { console.log(value); });
p.then(value => { console.log(value+' again')});
我们给Promise对象p制定了两个回调,那它都会执行么?我们看下控制台:
控制台顺序打印了两个回调里的输出,当且仅当Promise对象改变为对应状态时才会调用。
如果在Promise内部不调用resolve,那他的状态就是pending,这样我们没有改变状态所以一个then回调也不会执行
如果把这段代码改成这样,他还会是一样的结果吗:
let p = new Promise((resolve, reject) => { resolve('OK');});
p.then(value => { console.log(value);}).then(value => { console.log(value+' again');});
先看一下输出结果:
为什么第二个回调里的value是undefined呢?
因为在上面then方法的详解中提到过then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)
如果then方法的回调函数有返回数据,那么这个数据会作为下一个then的回调函数的参数,但是我们上面例子里的第一个then里没有return数据,所以第二个then里就不知道value是谁,如果我们在第一个then里加上return value,这样输出的结果就和原来一样啦。
3.改变状态与执行回调的顺序问题
都有可能, 正常情况下是先指定回调再改变状态, 但也可能先改状态再指定回调
当Promise对象的执行器当中的任务是同步任务,直接调用resolve,这种情况就是先改变Promise对象的状态,然后再去指定回调
如下例代码:
let p = new Promise((resolve,reject) => {
resolve('success');
});
p.then(value => {console.log(value);});
那什么时候then方法先执行,然后resolve改变状态后执行呢?
就是当执行器函数中是异步任务的时候, 也就是说,我改变状态需要等待一段时间,需要进入对应的异步任务队列去执行的时候,在这种情况下就是then先执行,resolve改变状态后执行
如下例代码:
let p = new Promise((resolve,reject) => {
setTimeout(() => {
resolve('success');
}, 1000);
});
p.then(value => {console.log(value);});
我们总结一下:
先指定回调再改变状态(异步
):
- 先指定回调
- 再改变状态
- 改变状态后才进入异步队列执行回调函数
先改状态再指定回调(同步
):
- 改变状态
- 指定回调 并马上执行回调
那么我们如何先改状态再指定回调呢?
注意:指定并不是执行
- 在执行器中直接调用 resolve()/reject() ,不使用定时器等方法,执行器内直接同步操作
- 延迟更长时间才调用 then() ,在 .then() 这个方法外再包一层例如延时器这种方法
那我们什么时候才能得到数据?
- 如果先指定的回调, 那当状态发生改变时, 回调函数就会调用, 得到数据
- 如果先改变的状态, 那当指定回调时, 回调函数就会调用, 得到数据
简单地说,如果Promise对象的执行器里是异步任务,比如说输出语句在定时器里的话,就先指定回调,但是指定不是执行。也得等Promise对象的状态发生改变时,才回去调用回调函数。
4.then方法返回结果由什么决定
前面我们说了,then方法方法返回的是一个新的Promise实例 ,那新的Promise对象的状态由什么来决定呢,它的返回结果的特点是什么呢?
简单表达:新Promise对象的状态由then()指定的回调函数执行的结果决定
详细表达:
1. 如果抛出异常, 新 promise 变为 rejected, reason 为抛出的异常
代码示例:
let p = new Promise((resolve,reject) => {
resolve('success');
});
let result = p.then(value => {
throw '出错啦';
})
console.log(result);
输出结果:
2.如果返回的是非 promise 的任意值, 新 promise 变为 resolved, value 为返回的值
代码示例:
let p = new Promise((resolve,reject) => {
resolve('success');
});
let result = p.then(value => {
return 'jack';
})
console.log(result);
输出结果:
3.如果返回的是另一个新 promise, 此 promise 的结果就会成为新 promise 的结果
代码示例:
let p = new Promise((resolve,reject) => {
resolve('success');
});
let result = p.then(value => {
return new Promise((resolve,reject) => {
reject('no');
})
})
console.log(result);
输出结果:
5.串联多个任务
1. promise 的 then()返回一个新的 promise, 可以看成 then()的链式调用
2. 通过 then 的链式调用串连多个同步/异步任务,这样就能用then()将多个同步或异步操作串联成一个同步队列
我们先看下面这段代码:
let p = new Promise((resolve,reject) => {
setTimeout(() => {
resolve('ok');
},1000)
});
p.then(value => {
return new Promise((resolve,reject) => {
resolve('success');
})
}).then(value => {
console.log(value);
})
输出结果:
因为第一个then返回的是一个新的Promise对象, 那么这个 promise 的结果就会成为新 promise 的结果,所以最后会输出success。
接下来我们对上面的代码再加一个then回调:
let p = new Promise((resolve,reject) => {
setTimeout(() => {
resolve('ok');
},1000)
});
p.then(value => {
return new Promise((resolve,reject) => {
resolve('success');
})
}).then(value => {
console.log(value);
}).then(value => {
console.log(value);
})
最后这个value的输出会是什么呢? 答案是 undefined
我们知道then的返回结果是一个Promise对象,它这个Promise对象的状态是由它指定的回调函数的返回值来决定的,第二个then的回调函数的返回值是空,就是undefined,undefined显然不是一个Promise类型的对象,我们之前说过,如果返回的是非 promise 的任意值, 新 promise 变为 resolved,也就是状态是成功的,那他的成功的结果就是返回的结果undefined。因为第二个then返回的Promise对象成功了,就会执行最后一个then的第一个回调,并且输出他成功的结果,也就是undefined。这就是then的链式调用
6.异常穿透
- 当使用 promise 的 then 链式调用时, 可以在最后指定失败的回调
- 前面任何操作出了异常, 都会传到最后失败的回调中处理
示例代码:
let p = new Promise((resolve,reject) => {
setTimeout(() => {
reject('出错啦');
},1000)
});
p.then(value => {
console.log(000);
}).then(value => {
console.log(111);
}).then(value => {
console.log(222);
}).catch(error => {
console.warn(error);
})
我们先看一下输出结果:
他输出的结果刚好是第一个Promise当中他失败的结果的值,中间的环节我们不需要管,不用指定失败的回调,只需要在最后指定一个失败的回调就可以处理失败的结果。这种现象就属于异常穿透。
比如我们在中间环节抛出一个错误,看看最后的catch能不能捕获到 :
let p = new Promise((resolve,reject) => {
setTimeout(() => {
resolve('ok');
},1000)
});
p.then(value => {
throw '出错啦';
}).then(value => {
console.log(111);
}).then(value => {
console.log(222);
}).catch(error => {
console.warn(error);
})
输出结果:
第一个then回调里抛出的错误是由最后的catch方法的回调来处理的,这就是异常穿透。
7.如何终断Promise链
当promise状态改变时,他的链式调用都会生效,那如果我们有这个一个实际需求:我们有4个then(),但其中有条件判断,如当我符合或者不符合第2个then条件时,要直接中断链式调用,不再走下面的then,该如何?
方法: 在该回调函数中返回一个 pending 状态的 Promise 对象
示例代码:
let p = new Promise((resolve,reject) => {
setTimeout(() => {
resolve('ok');
},1000)
});
p.then(value => {
console.log(111);
}).then(value => {
console.log(222);
return new Promise(()=>{})
}).then(value => {
console.log(333);
}).then(value => {
console.log(444);
})
输出结果:
示例分析:
中断Promise链的方法有且只有一种方式,那就是必须返回一个pending状态的Promise对象。因为如果返回的是一个成功的Promise对象,就算返回的结果都是undefined但是then里的回调还是会执行,唯独只有pending状态的Promise对象不一样,因为如果回调里返回一个pending,那么这个then返回的也是状态为pending的Promise对象,如果第一个(本题中)then返回的是pending状态的Promise对象,那后续的then方法的回调就都不能执行,因为状态没有改变,状态不改变就都不能执行。
简单地说:把当前状态变成pending,那么后面的then和catch就不执行
到这里我们Promise的第一部分就完成了,我们学习了Promise相关的API和一些关键问题,但是这些对于我们去大厂面试来说还远远不够。Promise的第二部分我们将试着去自定义封装Promise。我们先学会如何怎么去使用它,再从原理入手学着怎么去实现它。
希望看到这里的同学能够来个一键三连支持一下小杰同学!
版权声明
本文为[小杰学前端]所创,转载请带上原文链接,感谢
https://blog.csdn.net/qq_49900295/article/details/124290637
边栏推荐
猜你喜欢
MySQL 中的字符串函数
Clion installation tutorial
ArcGIS license error -15 solution
MySQL_ 01_ Simple data retrieval
Nat commun | current progress and open challenges of applied deep learning in Bioscience
re正则表达式
Re regular expression
C network related operations
[UDS unified diagnostic service] (Supplement) v. detailed explanation of ECU bootloader development points (2)
【ACM】70. 爬楼梯
随机推荐
Test post and login function
mysql自动启动设置用Systemctl start mysqld启动
MySQL 中的字符串函数
JD freefuck Jingdong HaoMao control panel background Command Execution Vulnerability
Implementation of image recognition code based on VGg convolutional neural network
YOLOv4剪枝【附代码】
ArcGIS table to excel exceeds the upper limit, conversion failed
Gst-launch-1.0 usage notes
Multi thread safe reference arc of rust
QT reading and writing XML files (including source code + comments)
MySQL_ 01_ Simple data retrieval
[UDS unified diagnostic service] IV. typical diagnostic service (6) - input / output control unit (0x2F)
Identification verification code
How to read literature
Stanford machine learning course summary
Operators in C language
JD-FreeFuck 京东薅羊毛控制面板 后台命令执行漏洞
Robocode tutorial 5 - enemy class
C# 的数据流加密与解密
Secure credit