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()
// 运行结果
// 发生错误