nodejs中的Promise对象
Promise
是一种有三个状态的对象(“执行中pending
”、“完成resolve
”、“失败reject
”),构造Promise
对象时的构造函数参数是一个有两个参数的函数,这个函数的两个参数分别对应着Promise
对象到达两个终点状态resolve
或reject
时要调用的函数。
一个典型的Promise
构造如下👇
new Promise(function(resolve, reject){
/*执行各类语句a*/
if(/*某个表示任务完成的判断条件*/)
resolve(/*上面的语句中产生的某个变量*/)
else
reject(new Error(/*某个错误*/))
})
2
3
4
5
6
7
👆当上面这个新建Promise
语句执行时,Promise
构造函数中的function(resolve, reject){}
会立即被执行,当这个函数在执行“/*执行各类语句a*/
”时,Promise
对象的状态为“pending
”;如果判断条件使函数执行到了resolve
函数,则Promise
对象状态变为resolve
;如果判断条件使函数执行到了reject
函数,则Promise
对象状态变为reject
。
为了让Promise
在我们想要的地方执行,一般把Promise
加个壳写成这样👇
const myPromise = function (some_value) {
return new Promise(function (resolve, reject) {
/*执行各类语句,用到some_value*/
if (/*某个表示任务完成的判断条件*/)
resolve(/*上面的语句中产生的某个变量v*/)
else
reject(new Error(/*某个错误e*/))
})
}
2
3
4
5
6
7
8
9
调用myPromise()
的返回值为一个新建的Promise
对象。
然后用的时候就这么写👇
myPromise(some_value)
关于return
的注意事项👇
# resolve和reject函数从何而来?
# 答曰:来自then
方法或者catch/final
方法。
在上面的Promise
例子中,如何告诉myPromise
resolve和reject都是什么函数?
# 正确姿势1👇
function resolve(v/*对应上面例子中的变量v*/){/*对v做点什么*/}
function reject(e/*对应上面例子中的错误e*/){/*对e做点什么*/}
myPromise(some_value).then(resolve,reject)
2
3
👆这样resolve和reject就会在构造myPromise的那个函数里面被执行了。这就相当于myPromise
执行完成之后把某个结果v
传递给了resolve
函数。然后这个then
的返回值就是那个function (resolve, reject) {}
的返回值。
# 正确姿势2👇
function resolve(v/*对应上面例子中的变量v*/){/*对v做点什么*/}
function reject(e/*对应上面例子中的错误e*/){/*对e做点什么*/}
myPromise(some_value).then(resolve).catch(reject)
2
3
👆起始then
里面可以不用写reject
用的那个函数,reject
函数可以写在.catch
方法里面,就像try{}catch(){}
错误处理一样,如果删了上面那个.catch(reject)
,当出错时new Error(/*某个错误e*/)
会真的作为错误被抛出来。
myPromise(some_value)
的返回值是一个Promise
对象,而myPromise(some_value).then(resolve,reject)
的返回值则是myPromise对象构造时里面那个function (resolve, reject) {}
的返回值。
在上面这个例子中,myPromise(some_value).then(resolve,reject)
和myPromise(some_value).then(resolve).catch(reject)
的返回值都为undefined
因为myPromise
里面的function (resolve, reject) {}
没有返回值。
注意,调用resolve或reject并不会终结 Promise 的参数函数的执行。
new Promise((resolve, reject) => {
resolve(1);
console.log(2);
}).then(r => {
console.log(r);
});
// 2
// 1
2
3
4
5
6
7
8
上面代码中,调用
resolve(1)
以后,后面的console.log(2)
还是会执行,并且会首先打印出来。这是因为虽然看起来resolve(1)
在console.log(2)
的前面,但是这其实只是告诉了Promise
当resolve
时要执行resolve(1)
,这个resolve(1)
语句会被保留直到console.log(2)
执行完并且函数退出后才会触发。一般来说,调用
resolve
或reject
以后,Promise
的使命就完成了,后继操作应该放到then
方法里面,而不应该直接写在resolve
或reject
的后面。所以,最好在它们前面加上return
语句,这样就不会有意外。
就像这样👇
const myPromise = function (some_value) {
return new Promise(function (resolve, reject) {
/*执行各类语句,用到some_value*/
if (/*某个表示任务完成的判断条件*/)
return resolve(/*上面的语句中产生的某个变量v*/)
else
return reject(new Error(/*某个错误e*/))
})
}
2
3
4
5
6
7
8
9
这时再调用myPromise(some_value).then(resolve,reject)
和myPromise(some_value).then(resolve).catch(reject)
的话就会有返回值了,因为myPromise
里面的function (resolve, reject) {}
有了返回值。并且按照上面那个写法,myPromise(some_value).then(resolve,reject)
和myPromise(some_value).then(resolve).catch(reject)
的返回值就是resolve(/*上面的语句中产生的某个变量v*/)
或者reject(new Error(/*某个错误e*/))
后面所有的代码都默认Promise
在resolve
或reject
处返回值。
# nodejs高玩的骚操作👇
# resolve
函数返回一个Promise
先来个简单的,让then
返回一个新的myPromise
const myPromise = function (some_value) {
return new Promise(function (resolve, reject) {
/*执行各类语句,用到some_value*/
if (/*某个表示任务完成的判断条件*/)
return resolve(v/*上面的语句中产生的某个变量v*/)
else
return reject(new Error(e/*某个错误e*/))
})
}
myPromise(value1).then(function(v){
return myPromise(v)
})
2
3
4
5
6
7
8
9
10
11
12
13
然后因为第一个then
的返回值变成一个Promise
了,它又可以再then
一次,所以我们就可以进一步这么写👇
function resolve(v/*对应上面例子中的变量v*/){/*对v做点什么*/}
function reject(e/*对应上面例子中的错误e*/){/*对e做点什么*/}
myPromise(value1).then(function(v){
return myPromise(v)
}).then(resolve,reject)
2
3
4
5
6
或者这么写👇
myPromise(value1).then(function(v){
return myPromise(v)
}).then(resolve).catch(reject)
2
3
这相当于是把myPromise(value1)
的结果传递给了又一个myPromise(v)
,然后再把myPromise(v)
的结果传递给resolve(v)
;并且myPromise(v)
在myPromise(value1)
到resolve
状态了之后才会执行。
# 出错了咋办?
注意到上面两个例子中的第一个then
没有指定reject
,这时如果有某一个myPromise
运行到reject
了,后面的then
都不会执行直到这个reject
碰到了某个then(resolve,reject)
或者catch(reject)
。如果后面没有then(resolve,reject)
或者catch(reject)
了?那就成为一个被抛出的错误。
# 骚操作x5
连6个myPromise
👇
myPromise(v1).then(function(v2){
return myPromise(v2)
}).then(function(v3){
return myPromise(v3)
}).then(function(v4){
return myPromise(v4)
}).then(function(v5){
return myPromise(v5)
}).then(function(v6){
return myPromise(v6)
}).then(resolve).catch(reject)
2
3
4
5
6
7
8
9
10
11
套4个myPromise
👇
myPromise(v1).then(function(v2){
return myPromise(v2).then(function(v3){
return myPromise2(v2,v3).then(function(v4){
return myPromise3(v2,v3,v4)
})
})
}).then(resolve).catch(reject)
2
3
4
5
6
7
嵌套和连接不一样的地方就在于,嵌套可以综合前面各个Promise
的返回值,连接只能获取前面一个。myPromise2
和myPromise3
定义如下👇。
const myPromise2 = function (v2,v3) {
return new Promise(function (resolve, reject) {
/*执行各类语句*/
if (/*某个表示任务完成的判断条件*/)
return resolve(v4/*上面的语句中产生的某个变量v4*/)
else
return reject(new Error(e/*某个错误e*/))
})
}
const myPromise2 = function (v2,v3,v4) {
return new Promise(function (resolve, reject) {
/*执行各类语句*/
if (/*某个表示任务完成的判断条件*/)
return resolve(v5/*上面的语句中产生的某个变量v5*/)
else
return reject(new Error(e/*某个错误e*/))
})
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# async/await
随着 Node 7 的发布,越来越多的人开始研究据说是异步编程终级解决方案的 async/await。async 是“异步”的简写,而 await 可以认为是 async wait 的简写。所以应该很好理解 async 用于申明一个 function 是异步的,而 await 用于等待一个异步方法执行完成。
如果需要通过 await 来调用一个 async 函数,那这个调用的外面必须得再包一个 async 函数,然后……进入死循环,永无出头之日……
# async
一个单独的async有什么用?请看👇
async function testAsync(i) {
if(i)return "hello async" + i;
else throw new Error("Oops!");
}
const result = testAsync(1);
console.log(result);
const another = testAsync();
2
3
4
5
6
7
输出👇
Promise { 'hello async' }
和一个错误👇
UnhandledPromiseRejectionWarning: Error: Oops!
看到输出就恍然大悟了—— 输出的是一个 Promise 对象。
这样我们就可以理解了,async把一个函数变成了Promise,并将return
的内容放到了resolve
中,throw
的内容放到了reject
中,它就是一种更加方便而直观地定义Promise
的方法。因此,上面这个函数就应该这样用:
testAsync(1).then((r)=>{console.log(r)}).catch((e)=>{console.log(e)});
testAsync().then((r)=>{console.log(r)}).catch((e)=>{console.log(e)});
2
# await
await的使用分两种情况,第一种是await的表达式不是Promise
时,这时有没有await都一样。比如下面这段代码输出3👇
async function test(i) {
let res = await i + 1;
console.log(res);
};
test(2);
2
3
4
5
而第二种是await的表达式是Promise
的时候,这时它会把Promise
中resolve
的输入作为结果返回,而将reject
的输入作为错误抛出。比如上一节用then链调用的async
函数的代码等效于👇
async function test() {
try {
const r = await testAsync(1);
console.log(r);
await testAsync();
} catch(e) {
console.log(e)
}
}
test();
2
3
4
5
6
7
8
9
10
从上面可以看出,加了await的Promise
就像一个普通的函数一样,立即返回了他的处理结果,await可以看作是一种调用Promise
的简便方法。但是我们知道,大多数情况下,resolve
在Promise
中是异步调用的,即它不会立即返回,那await是怎么实现把一个Promise
的resolve
立即返回的呢?很简单,它阻塞了后面的代码直到resolve
返回。这相当于把await后面的代码全部放到then
链定义的resolve
里面了,也就是说,上面的代码等效于👇
function test() {
return new Promise((resolve,reject) => {
testAsync(1).then((r)=>{
console.log(r);
return testAsync().then(()=>{
resolve()
});
}).catch((e)=>{
console.log("catched!")
console.log(e)
})
})
}
test();
2
3
4
5
6
7
8
9
10
11
12
13
14
比起async/await
代码,Promise
代码难度不少,不是吗?
关于try/catch
的位置,此处有一个易错点,比如像这样把try/catch
放到外面来的做法是捕捉不到我们想要的错误的👇
async function test() {
const r = await testAsync(1);
console.log(r);
await testAsync();
}
try {
test();
} catch(e) {
console.log("catched!")
console.log(e)
}
2
3
4
5
6
7
8
9
10
11
为什么捕捉不到也很好理解,因为前面标了async
的函数返回的是一个Promise
,它的错误要在.catch((e)=>{})
里面或者用await
才能捕捉到,就像这样👇
test().catch((e)=>{console.log("catched!");console.log(e)})
这里还有一个注意点,那就是异步代码是不能向同步代码抛出错误的,比如上面那段进入到catch
中的错误不能再抛出到外层的同步代码中,比如这段代码也是不能正确捕捉到错误的👇
try{
test().catch((e)=>{throw e})
} catch(e) {
console.log("catched!")
console.log(e)
}
2
3
4
5
6
即使用事件触发也是一样不能捕捉的
const events = require("events")
let emitter = new events.EventEmitter()
try{
emitter.on("error",(e)=>{throw e})
} catch(e) {
console.log("catched!")
console.log(e)
}
test().catch((e)=>{emitter.emit("error",e)})
2
3
4
5
6
7
8
9
异步代码的错误只能用异步代码捕捉到。