Promise的基本概念
Pomise是个对象,用来封装异步操作,让开发人员以更简洁明了的方式书写异步操作的处理方式。
定义Promise对象的时候,需要向它的构造函数传递一个函数,这个函数封装了异步操作,这个函数称为执行器函数。执行器函数接受系统定义的两个函数作为参数(resolve和reject),new完成后,构造函数会立即调用执行器函数,同时系统会传入上述两个函数的地址,开发人员就可以在执行器函数内调用resolve和reject函数。
每个Promise对象都有三种状态:pending,fullfilled和rejected。Promise对象在刚构造完成的时候,处于pending状态。如上文所述,在执行器函数内,可以通过调用resolve或者reject函数来转换Promise对象的状态。当reject函数被调用,它会抛出异常,应该在代码中处理这个异常。
每个Promise对象的状态只能转换一次,完成后,这个对象的状态就凝固了,无法再转换。在Promise的执行器函数内,如果把resolve和reject都呼叫了,或者多次调用他们,他们不会都生效,只会排在前面的那一个会生效,但不会阻塞后面的代码。意思是,resolve 和 reject函数并不会终止执行器函数的执行,他们后面的代码仍然会执行。
Promise实例生成以后,可以用then方法传入resolved状态和rejected状态的回调函数。也就是说,Promise对象的状态发生变化后,这些回调函数会被系统调用,可以在这两个回调函数里得到转换状态之后的结果。
一般来说,调用resolve或reject以后,Promise 的使命就完成了,后继操作应该放到then方法里面,而不应该直接写在resolve或reject的后面。所以,最好在它们前面加上return语句,这样就不会有意外。
const p = new Promise(
(resolve, reject) => {
console.log('这是Promise执行器函数输出的内容。')
}
)
// Promise的参数内含参数new
Promise(
(resolve,reject) => {
// 异步操作代码本身放在这里,只发起异步操作,但不会处理返回的数据
}).then(
// 异步操作完成后的回调函数放在这里
)
// 以上程序输出结果为:
这是Promise执行器函数输出的内容。
new Promise(
(resolve,reject) => {
console.log('开始异步操作')
setTimeout(resolve('这是异步操作返回的数据'),1000)
}).then(
(res) => {console.log('异步操作:',res)})
// 上述程序输出结果为:
开始异步操作
异步操作:
这是异步操作返回的数据
另一种方法:
new Promise(
(resolve,reject) => {
console.log('开始异步操作')
setTimeout(
(x=10) => {
//箭头函数使用了默认参数
if(x>0)
resolve(x)
else
reject('错误')
},
2000)
}).then(
(res) => console.log('异步操作完成,返回结果是:',res),
(err) => console.log(err))
then方法
它的作用是为 Promise实例添加状态改变后的回调函数。then方法内的return语句,调用后就会返回的是一个新的Promise实例,js会自动转化为return new Promise。
采用链式的then,可以指定一组按照次序调用的回调函数。这时,前一个回调函数,有可能返回的还是一个Promise对象(即有异步操作),这时后一个回调函数,就会等待该Promise对象的状态发生变化,才会被调用。
Promise主要用来解决异步操作太多、回调函数过于复杂的问题,例如以下ajax操作:
$.ajax({
url: 'http://www.xxxx.com/savedata',
data: {name:'user1'},
success: function(data){
console.log('保存成功');
},
error:function(err){
console.log(err);
}
}
)
可以使用Promise改写成:
function asycnSave(){
const save = new Promise(
(resolve,reject)=>{
let url = 'http://www.xxx.com/savedata';
data = {new: 'user1'};
$.post(url,data);
}
);
return save;
}
save().then(
(data) => {
console.log('保存成功')}
);
如果没有return 语句,下一个then的回调函数会不会得到一个resolved状态的Promise?
试验证明:会得到一个resolved状态的Promise,只是resolved值是undefined。
const p = new Promise((resolve,reject) => {
console.log('执行器函数执行。')
resolve('状态转化为resolved')
})
p.then(res => {
console.log('成功状态的回调,输出resolved值:', res)
}).then(res => {
console.log('第二个then的resolved回调:',res)
})
/* 运行结果
执行器函数执行。
成功状态的回调,输出resolved值: 状态转化为resolved
第二个then的resolved回调: undefined
*/
catch方法
Promise.catch()方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。如果异步操作抛出错误,状态就会变为rejected,系统就会调用catch()方法指定的回调函数来处理这个错误。另外,then()方法指定的回调函数,如果运行中抛出错误,也会被下一个catch()方法捕获,这点比较重要。
经试验,如果通过单独调用then方法传入了成功的回调函数,同时,而没有传入失败的回调函数,那么,即使后来补充定义了catch函数,js仍然会抛出一个错误。
const p = new Promise((resolve, reject) => {
reject('发生错误')
})
// 单独调用then传递方法
p.then(res => {
console.log(res)
})
p.catch(error => {
console.log(error)
})
// 运行结果如下:
没有调用
node:internal/process/promises:288
triggerUncaughtException(err, true /* fromPromise */);
^
[UnhandledPromiseRejection: This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason "没有调用".] {
code: 'ERR_UNHANDLED_REJECTION'
}
Node.js v18.17.1
在promise中抛出错误,与调用reject方法的效果一样,可以被catch方法捕捉到。
const promise = new Promise(function(resolve, reject) {
throw new Error('发生错误')
});
promise.catch(function(error) {
console.log(error);
});
一般来说,不要在then()方法里面定义 Reject 状态的回调函数(即then的第二个参数),总是使用catch方法。
跟传统的try/catch代码块不同的是,如果没有使用catch()方法指定错误处理的回调函数,Promise 对象抛出的错误不会传递到外层代码,即不会有任何反应。不会退出进程、终止脚本执行,例如以下示例,2 秒之后还是会输出123。这就是说,Promise 内部的错误不会影响到 Promise 外部的代码,通俗的说法就是“Promise 会吃掉错误”。但是在node中,以下代码是会被终止的。
从以下这个实例也可以看出,如果在Promise内发生了其他错误,Promise对象会被设置为rejected状态,那么相应的错误处理函数会被调用。即使调用了resolve函数也没有用。
const someAsyncThing = function() {
return new Promise(function(resolve, reject) {
// 下面一行会报错,因为x没有声明
resolve(x + 2);
});
};
someAsyncThing().then(function() {
console.log('everything is great');
});
setTimeout(() => { console.log(123) }, 2000);
// Uncaught (in promise) ReferenceError: x is not defined
// 123
catch()方法返回的还是一个 Promise 对象,因此后面还可以接着调用then()方法,以及处理错误:
const someAsyncThing = function() {
return new Promise(function(resolve, reject) {
// 下面一行会报错,因为x没有声明
resolve(x + 2);
});
};
someAsyncThing()
.catch(function(error) {
console.log('oh no', '发生错误');
return Promise.reject('hello,又错了')
})
.then(function(e) {
console.log(e);
}).catch(e=>console.log(e));
// 运行结果:
oh no 发生错误
hello,又错了
catch/then内声明的函数按顺序执行,catch只会执行一次(如果定义多个catch的话)。
综合案例:链式调用的执行和参数传递问题
在链式调用中,如果对象的状态被resolve或者reject了,那么它后面所有的then方法内的相应回调函数都会被调用。
在then方法内调用return语句,会像构造函数里的resolve或者reject函数一样,把结果传递给下一个then方法内的函数。请看以下示例:
console.log('本文件研究,是否没有主动调用构造函数的resolve和reject参数代表的函数,在后续的then和catch内就不会调用响应的函数')
new Promise((resolve, reject) => {
console.log('【第1个例子。研究没有在构造函数内显示调用resolve和reject的情况】这里是Promise的构造函数。在这个例子里,声明了then函数和catch函数,但没有在构造函数内显示调用resolve和reject,所以then和catch不会被调用')
setTimeout(() => {
console.log('3秒后,第1个例子的异步操作完成')
}, 3000)
}).then(
(res) => {
console.log('then函数得到了resolve的结果:', res)
}
).catch((err) => { console.log('catch被调用,得到了reject的内容')})
new Promise((resolve, reject) => {
console.log('【第2个例子。研究显示调用resolve的情况。结论是,如果没有显示调用resolve和reject函数,那么在下一步的then和cath函数内相应的函数不会被调用。】这里是Promise的构造函数。在这个例子里,声明了then函数和catch函数,而且在构造函数内显示调用了resolve,所以then的第一个默认函数被调用了,并得到了resolve输出的结果。但是在构造函数内没有调用reject,所以catch函数没有被调用。')
setTimeout(() => {
console.log('3秒后,第二个例子的异步操作完成')
resolve('【第二个例子resolve的结果】')
}, 3000)
}).then(
(res) => {
console.log('then函数得到了resolve的结果:', res)
}
).catch((err) => { console.log('catch被调用,得到了reject的内容')})
new Promise((resolve, reject) => {
console.log('【第3个例子。研究reject后,then函数内的第二个参数和catch函数的调用顺序。结论是,如果then的第二个参数函数被定义了,那么catch函数不会被调用。】这里是Promise的构造函数。在这个例子里,声明了then函数和catch函数,而且在构造函数内显示调用了resolve,所以then会被调用了,并得到了resolve输出的结果。但是在构造函数内没有调用reject,所以catch函数没有被调用。')
setTimeout(() => {
console.log('3秒后,第三个例子的异步操作完成')
reject('【第三个例子reject的结果】')
}, 3000)
}).then(
(res) => {
console.log('then函数得到了resolve的结果:', res)
},
(rej) => {
console.log('then函数的第二个参数函数被调用,得到了reject的结果:', rej)
}
).catch((err) => { console.log('catch被调用,得到了reject的内容:', err)})
console.warn('从上面可以看出,构造函数内的语句是立即执行的,而then和catch函数是异步的,要在立即执行函数后才会执行。')
以上示例输出以下结果:
本文件研究,是否没有主动调用构造函数的resolve和reject参数代表的函数,在后续的then和catch内就不会调用响应的函数
【第1个例子。研究没有在构造函数内显示调用resolve和reject的情况】这里是Promise的构造函数。在这个例子里,声明了then函数和catch函数,但没有在构造函数内显示调用resolve和
reject,所以then和catch不会被调用
【第2个例子。研究显示调用resolve的情况。结论是,如果没有显示调用resolve和reject函数,那么在下一步的then和cath函数内相应的函数不会被调用。】这里是Promise的构造函数。
在这个例子里,声明了then函数和catch函数,而且在构造函数内显示调用了resolve,所以then的第一个默认函数被调用了,并得到了resolve输出的结果。但是在构造函数内没有调用reject,所以catch函数没有被调用。
【第3个例子。研究reject后,then函数内的第二个参数和catch函数的调用顺序。结论是,如果then的第二个参数函数被定义了,那么catch函数不会被调用。】这里是Promise的构造函数
。在这个例子里,声明了then函数和catch函数,而且在构造函数内显示调用了resolve,所以then会被调用了,并得到了resolve输出的结果。但是在构造函数内没有调用reject,所以catch函数没有被调用。
从上面可以看出,构造函数内的语句是立即执行的,而then和catch函数是异步的,要在立即执行函数后才会执行。
3秒后,第1个例子的异步操作完成
3秒后,第二个例子的异步操作完成
then函数得到了resolve的结果: 【第二个例子resolve的结果】
3秒后,第三个例子的异步操作完成
then函数的第二个参数函数被调用,得到了reject的结果: 【第三个例子reject的结果】
sync 和 await 语法
async用法
async是一个函数修饰符,被async修饰的函数会返回一个Promise对象。因此对async函数返回值可以直接使用then方法传入有关回调函数。
返回resolved状态的Promise对象
在async函数内执行return语句,会返回一个resolved状态的Promise对象,return后面的值就是resolved的值,可以在then传入的resolved回调函数中接收到该值。
async function myFunction() {
return "Hello";
}
等价于:
async function myFunction() {
return Promise.resolve("Hello");
}
即使没有明确执行return语句,async函数还是返回一个resolved状态的Promise对象,只不过返回值是undefined。
f1 = async () => {
// async函数内没有return语句
}
f1().then(
(res) => {
// resolved状态的回调函数还是会被调用
console.log('调用了resolved状态的回调函数:', res)
},
(rej) => {
console.log('调用了rejected状态的回调函数:',rej)
}
)
// 运行结果
// *调用了resolved状态的回调函数: undefined*
返回rejected状态的Promise对象
如果async函数抛出了错误,那么它将返回一个rejected状态的Promise对象,可以使用catch方法或者在then方法中传入回调函数处理。
f2 = async () => {
throw new Error('错误')
}
f2().catch(
rej => console.log(rej)
)
// 运行结果
// Error: 错误
await用法
await 是一个命令,只能放在async函数内。它的作用是等待异步操作的完成,从而返回该结果。它具有return语句的功能,只不过返回的功能更复杂。
await命令和async修饰符不一样,它不会将返回值包装成Promise对象。
如果跟在await 命令后面的是一个Promise对象,那么await命令可以得到该对象返回的值(resolve或reject抛出的结果),且取到值后语句才会往下执行;就是说,await会阻塞后面的代码。
如果跟在await 命令后面的不是Promise对象,会直接返回这个值。
如果await后面的操作发生错误,则会抛出异常,或者说返回了一个rejected状态的Promise对象,则需要调用try/catch结构来处理错误。
f3 = async () => {
return Promise.reject('发生错误')
// 或者
throw new Error('发生错误')
}
f4 = async () => {
try { await f3() }
catch (e) {
console.log(e)
}
}
f4()
// 运行结果
// 发生错误
本文介绍了Promise对象的概念,如何通过构造函数和执行器函数处理异步操作,状态转换规则,then和catch方法的应用,以及async和await语法在Promise中的使用。
5440

被折叠的 条评论
为什么被折叠?



