Promise使用,看完你一定会有所收获

本文详细探讨了Promise的使用,包括其初识、状态变化、then方法的特性,以及宏任务和微任务的关系。通过实例展示了如何避免回调地狱,解释了Promise状态的不可变性,并讨论了异常处理的多种方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

测试环境为Node.js/谷歌浏览器,如若没有声明,默认环境为Node.js

Promise语法出现前,我们是这样使用具有依赖关系的异步函数的,以读取文件内容为例:

let fs = require("fs");

fs.readFile('./1.txt','utf-8',(err, result1) => {
	console.log(result1);
	fs.readFile('./2.txt','utf-8',(err, result2) => {
		console.log(result2);
		fs.readFile('./3.txt','utf-8',(err, result3) => {
			console.log(result3);
		});
	});	
});

这就是我们通常所说的回调地狱,在结构上有着极不便于阅读的缺点,为此Promise的出现为我们在结构上做了极大的优化



Promise的初使用

Promise是什么?

对象

promise对象通过new直接创建

//let p = new Promise();  没有参数会报错
let p = new Promise((resolve,reject) => {}); //通常传入一个匿名函数,函数参数一般为resolve和reject

试着打印一下这个对象

let p = new Promise((resolve,reject) => {}); 
console.log(p);		//=> Promise { <pending> }; 	:Node.js
console.log(p);		//=> Promise {<pending>}; 	:谷歌浏览器

Promise的状态

Promise一共有三种状态,上面打印的是Promise的第一种状态pending(等待)

以下是Promise的三种状态,任何一个Promise对象的状态有且仅有以下一种

  • pending(等待)
  • resolve(成功)
  • reject(拒绝)

打印一个Promise对象
在谷歌浏览器分别实现为

Promise { <pending> }
Promise { <fullfilled>: (参数) }
Promise { <rejected>: (参数) }

在Node.js分别实现为

Promise { <pending> }
Promise { (参数) } 
Promise { <rejected>: (参数) }

在Node.js中分别简单地实现Promise的三种状态

let p1 = new Promise((resolve, reject) => {
						  //不做任何处理,默认返回pending状态的Promise
});

console.log(p1);	//=> Promise { <pending> }


let p2 = new Promise((resolve, reject) => {
	resolve("second"); 	//用resolve函数返回的都是resolve状态的Promise,参数为second
});

console.log(p2);	//=> Promise { 'second' }


let p3 = new Promise((resolve, reject) => {
	reject("thrid"); 	//用reject函数返回的都是reject状态的Promise,参数为thrid
});  //因为抛出了拒绝状态而没有处理,所以这里会报错,可以暂时不用管,不影响我们的结果

console.log(p3);	//=> Promise { <rejected> 'third' }

那么Promise的状态有什么用呢?

传递给后面的then函数处理携带的参数


then()

关于它的使用只需要记住一点:
then方法是跟在Promise对象后面使用的

关于then的特点,需要记住一下几点(最后会做验证)
1.then方法处理上一个Promise的状态,并返回一个新的resolve状态(如果上一个Promise状态不为pending的话,否则返回pendding状态的Promise),参数为undefined

2.then方法默认返回的也是一个Promise对象,因此then是可以链式使用的

2.如果then方法没有处理Promise的状态,那么这个Promise的状态会继续向下传递,给下面的then处理

then方法可以接收一个或者两个函数,一般都是匿名函数

/*接收两个函数,这两个函数可以分别处理上一个Promise的状态*/
new Promise((resolve, reject) => {

}).then(value => {	//假如Promise的状态为resolve,那么value为对应resolve参数的值
	console.log(value);
},reason => {		//假如Promise的状态为reject,那么value为对应reject参数的值
	console.log(reason);
})

/*接收一个函数*/
new Promise((resolve, reject) => {

}).then(value => {	//假如Promise的状态为resolve,那么value为对应resolve参数的值
	console.log(value);
})
/*或者*/
new Promise((resolve, reject) => {

}).then(null,reason => {	//假如Promise的状态为reject,那么value为对应reject参数的值
	console.log(reason);
})

来一个实例

说了这么多,是时候上一个例子了,还是以读一个文件为例子
事先向同目录下的text.txt文件内写入Hello Promise

/*读取文件操作*/
let fs = require("fs"); 
new Promise((resolve, reject) => {
	fs.readFile("./text.txt","utf-8", (err, result) => {
		if(!err) {
			resolve(result);
		}else {
			reject(err);
		}
	})
}).then(value => {
	console.log(value); //=> Hello Promise
},reason => {
	console.log(reason);
})

接下来模拟文件找不到的情况,可见Promise语法对其处理良好:

/*读取文件操作*/
let fs = require("fs"); 
new Promise((resolve, reject) => {
	fs.readFile("./123.txt","utf-8", (err, result) => { //123.txt为不存在的文件
		if(!err) {
			resolve(result);
		}else {
			reject(err);
		}
	})
}).then(value => {
	console.log(value); 
},reason => {
	console.log(reason); //=> Error: ENOENT: no such file or directory, open 'C:\Users\Admin......\123.txt'
})

以上所学的不用Promise语法我们也完全可以实现,但是Promise做的就只有这么多了吗?


Promise不止如此

接下来我们引入宏队列与微队列的概念

在学习Js同步和异步的时候,我们知道像setTimeout等异步函数会被放在一个分线程,等主线程的函数执行完了才执行异步函数,如下:
在这里插入图片描述
事实上,分线程还可以进一步划分为宏队列与微队列,他们也有有不同的执行优先级
在这里插入图片描述
微队列的函数是优于宏队列执行的
现在我们有三个执行等级了:
主线程 > 微队列 > 宏队列

注意:
上图所说的微队列里面的是Promise的回调函数,也就是执行完Promise对象后的then()方法
我们来一个测试:

new Promise((resolve, reject) => { 
	console.log("A");			//1. 按照从上到下的顺序,首先输出A
	resolve("C");
}).then(value => {
	console.log(value);			//3.这里才是Promise的回调,属于微队列,输出resolve的参数"C"
},reason => {
	console.log(reason);
})

setTimeout(()=> {
	console.log("D");			//4.setTimeout属于宏队列,最后执行
},0);

console.log("B"); 			   //2. 按照从上到下的顺序,其次输出B

/*
A
B
C
D
*/	

宏任务的提升原来是误解

我们再来看一个例子,不知道是否与你想的一致(许多人会误解为ABDC)

new Promise((resolve, reject) => {
	setTimeout(()=> {
		resolve(); //微任务是在宏任务执行过程中创建出来的,因此先输出C再输出D
		console.log("C");
	},0)
	console.log("A");
	
}).then(value=> {
	console.log("D")
},reason => {
	console.log(reason)
})
console.log("B");
/*
A
B
C
D
*/

现在,我们知道Promise也有异步机制,接下来你就会发现Promise能很好的解决回调地狱的问题


封装Promise

let fs = require("fs");

function file(url, code="utf-8") { //封装Promise,其核心是返回Promise对象,让其后面可以调用then方法
	return new Promise((resolve, reject) => {
		fs.readFile(url, code, (err, result) => {
			if(!err) {
				resolve(result);
			}else {
				reject(err);
			}
		})
	})
}
/*
 *我们在txt文件夹下准备三个文件1.txt、2.txt、3.txt
 *内容分别为: "第一个文件输出了","第二个文件输出了","第三个文件输出了"
 */
file("../txt/1.txt")
.then(value => {
	console.log(value);
	return file("../txt/2.txt");
}).then(value => {
	console.log(value);
	return file("../txt/3.txt");
}).then(value => {
	console.log(value);
}).catch(err => {
	console.log(err);
})

/*
第一个文件输出了
第二个文件输出了
第三个文件输出了
 */

你需要知道的一些细节

现在我们已经掌握了Promise解决回调地狱的问题,但是还有许多关于Promise的细节需要我们了解
上文提到Promise的细节,现在我们一一来验证

1.then方法处理上一个Promise的状态,并返回一个新的resolve状态(如果上一个Promise状态不为pending的话,否则返回pendding状态的Promise),参数为undefined
看着虽然复杂,下面一一进行举例:

/*没有then方法处理Promise状态*/
let p1 = new Promise((resolve, reject) => {
	resolve("hello");		//调用了resolve,初始状态为resolve
})
console.log(p1);	
/*
 Promise { 'hello' }
*/


/*then处理Promise<resolve>的状态*/
let p2 = new Promise((resolve, reject) => {
	resolve("hello");		//调用了resolve,初始状态为resolve
}).then(value => {
	console.log("resolve处理完毕")
},reason => {
	
})
setTimeout(() => {	//在微任务全部执行完毕后,也就是then执行完毕后再打印p2
	console.log(p2);
}) 				
/*
 resolve处理完毕
 Promise { undefined }   //处理后返回一个新的resolve状态,参数为undefined
*/


/*then处理Promise<reject>的状态*/
let p3 = new Promise((resolve, reject) => {
	reject("hello");	//调用了reject,初始状态为reject
}).then(value => {
	console.log("resolve处理完毕")
},reason => {
	console.log("reject处理完毕")
})
setTimeout(() => {	//在微任务全部执行完毕后,也就是then执行完毕后再打印p2
	console.log(p3);
}) 
/*
 reject处理完毕 
 Promise { undefined }   //处理后还是返回一个新的resolve状态,参数为undefined		
*/


/*then不会处理pending状态的promise,因此pending状态继续向下传递*/
let p4 = new Promise((resolve, reject) => {
					//没有调用resolve或者reject,状态为pending
}).then(value => {
	console.log("处理完毕")
},reason => {
	
})
setTimeout(() => {	//在微任务全部执行完毕后,也就是then执行完毕后再打印p2
	console.log(p4);  
}) 
/*
 Promise { <pending> }
*/

2.then方法默认返回的也是一个Promise对象,因此then是可以链式使用的
第一点其实已经表明了:then方法默认返回的也是一个Promise对象,在我们输出p1、p2、p3、p4的时候,看到的就是经过then方法处理过状态的一个新的Promise

关于第二点,只需要记住一点:在多个then链式使用时,某一个then返回的Promise的状态取决于上一个Promise的状态和这个then有没有处理上一个Promise的状态。
举例:

/*某一个then返回的Promise的状态取决于上一个Promise的状态(---resolve状态)和这个then有没有处理上一个Promise的状态(---没有)*/
let p1 = new Promise((resolve, reject) => {
	resolve("hello");
})
let p2 = p1.then();
setTimeout(() => {	
	console.log(p1);
	console.log(p2);
}) 
/*
Promise { 'hello' }  //初始状态为:<resolve>:"hello"
Promise { 'hello' }  //then不做处理,返回与上一个Promise相同状态:<resolve>:"hello"
*/

/*某一个then返回的Promise的状态取决于上一个Promise的状态(---resolve状态)和这个then有没有处理上一个Promise的状态(---处理了)*/
let p3 = new Promise((resolve, reject) => {
	resolve("hello");
})
let p4 = p3.then(value => {},reason => {});
setTimeout(() => {	
	console.log(p3);
	console.log(p4);
}) 
/*
Promise { 'hello' }  //初始状态为:<resolve>:"hello"
Promise { undefined }  //then做处理,返回<resolve>:undefined
*/

/*then不能处理pending状态的Promise,因此这个状态会一直传递下去*/
let p5 = new Promise((resolve, reject) => {
	
})
let p6 = p5.then()
.then()
.then();  
setTimeout(() => {	
	console.log(p5);
	console.log(p6);
})
/*
Promise { <pending> }
Promise { <pending> }
*/

3.如果then方法没有处理Promise的状态,那么这个Promise的状态会继续向下传递,给下面的then处理
第三点比较好理解,如下:

let p1 = new Promise((resolve, reject) => {
	reject("error");	//初始状态为reject
})
let p2 = p1.then()      
.then()
.then();			//没有用then处理reject状态

let p3 = p2.then(value => {},reason => {});  //then处理了p2的reject状态,返回给p3
setTimeout(() => {	 
	console.log(p1);
	console.log(p2);
	console.log(p3);
})
/*
Promise { <rejected> 'error' }	//初始状态为reject
Promise { <rejected> 'error' }	//没有用then处理reject状态,因此p2接收到传递下来的reject状态
Promise { undefined }		//then处理了p2的reject状态,默认传递给下一个的状态为Promise { undefined }(特点一)
*/

如果你看了上述例子却还是很懵,那么你需要知道另一件事,或许你就能明白

Promise的状态不可变性

一旦确定了某个状态(不为pending,可以是resolve或者reject),那么这个Promise对象的状态将不会改变
有的小伙伴感到很疑惑,状态不是可以处理吗,然后可以改变吗?我们来看一个例子你就明白了

let p1 = new Promise((resolve, reject) => {
	resolve("hello")
})
console.info("处理前p1的状态: ")
console.log(p1);
let p2 = p1.then(
value => {
	console.log(value);
},reason => {
	console.log(reason);
});
setTimeout(() => {
	console.log('------------------------------------------')
	console.info("处理后p1的状态: ");
	console.log(p1);
	console.info("用then处理p1后,p1.then()的状态: ")
	console.log(p2);
})
/*
处理前p1的状态:
Promise { 'hello' }
hello
------------------------------------------
处理后p1的状态:
Promise { 'hello' }				//状态不可变性
用then处理p1后,p1.then()的状态:
Promise { undefined }       	//实际上,上面我们看到的状态改变都是then()的返回值,并不是上一个Promise的状态
*/

Promise的异常处理:

我们知道使用reject()可以使Promise的状态由<pending>变为<reject>状态,并向下转递此状态
但其实还有几种方式都可以使Promise的状态由<pending>变为<reject>状态

let p1 = new Promise((resolve, reject) => {
	//reject("error"); 	
	//throw new Error("发生错误");	//抛出异常,可以抛出Error的实例,或者继承Error类的类的实例
	//Hd +; 					//语法错误
}).then(value => {
	console.log(value);
},reason => {
	console.log("err: " + reason); //上面三种错误都可以被此条语句捕获处理
})

catch:
catch方法同then方法的第二个函数的作用是一样的,我们通常用来对链式then方法调用产生的语法错误进行统一处理

封装Promise的时候,我们用到了catch,现在我们对catch做一个优化

let fs = require("fs");

function file(url, code="utf-8") { //封装Promise,其核心是返回Promise对象,让其后面可以调用then方法
	return new Promise((resolve, reject) => {
		fs.readFile(url, code, (err, result) => {
			if(!err) {
				resolve(result);
			}else {
				reject(`打开文件${url}错误`);  //明确某一个文件错误
			}
		})
	})
}
/*
 *我们在txt文件夹下准备三个文件1.txt、2.txt、3.txt
 *内容分别为: "第一个文件输出了","第二个文件输出了","第三个文件输出了"
 */
file("../txt/1.txt")
.then(value => {
	console.log(value);
	return file("../txt/22.txt");  //故意输错路径
}).then(value => {
	console.log(value);
	return file("../txt/3.txt");
}).then(value => {
	console.log(value);
}).catch(err => {
	console.log(err); //捕获所有错误并处理
})

/*
我是第一个
打开文件../txt/22.txt错误
 */
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值