如何使用promise
这一部分自己看MDN文档即可。
这里我就讲一下all和race方法,all和race也是函数对象上的方法。
all方法传入一个promise实例数组,只有当所有的promise全部成功(resolved)才会触发onResolved回调,否则,当遇到第一个rejected的promise对象时,触发onRejected回调,并将该promise传入的值给到reason。例子如下:
var p1 = Promise.resolve(1);
var p2 = Promise.resolve(2);
var p3 = Promise.reject(3);
var p4 = Promise.reject(4);
Promise.all([p1, p2, p3, p4]).then(
(result) => {
console.log("result", result);
},
(reason) => {
console.log("reason", reason); //reason 3
}
);
race方法同样传入一个promise数组,只要有第一个promise改变了状态,无论是resolved还是rejected,都会触发相应的回调,正如这个单词的中文意思:竞跑。
Promise.race([p1, p2, p3, p4]).then(
(result) => {
console.log("race result", result); //race result 1
},
(reason) => {
console.log("race reason", reason);
}
);
promise的几个关键问题
关键问题一:如何改变promise的状态?
1. resolve(value):如果当前promise是pending状态,则变为resolved
2. reject(reason):如果当前promise是pending状态,则变为rejected
3. 抛异常:如果当前promise是pending状态,则变为rejected
这里可以演示一下不常见的第三种情况
new Promise((resolve,reject)=>{
throw new Error('what fuck')
})
.then(null,(reason)=>{
console.log(reason);
})
//Error: what fuck
一个promise指定多个成功/失败的回调,当触发成功(失败)时,是触发全部的成功(失败)回调还是最后设置的那个回调?
全部的。例子如下:
var p = new Promise((resolve, reject) => {
throw new Error("what fuck");
});
p.then(null, (reason) => {
console.log("reason1:", reason);
});
p.then(null, (reason) => {
console.log("reason2:", reason);
});
//output
//reason1: Error: what fuck
//reason2: Error: what fuck
关键问题二:改变promise状态和指定回调函数的先后顺序
这个先后顺序要具体代码具体分析,比如如下这段代码:
new Promise((resolve,reject)=>{
setTimeout(() => {
resolve('hello')
}, 1000);
})
.then((val)=>{
console.log('val',val);
})
这段代码就是先指定回调再改变状态。执行构造函数时,遇到setTimeout于是把该延时任务放到任务队列里去,然后继续执行then方法,于是指定了回调函数,等第一次同步代码执行完后从任务队列中取出延时任务并执行,执行完resolve改变状态。注意,then方法里的回调函数是异步回调,所以它是在promise改变状态后才执行的,但指定回到函数是同步代码做的事情,实际上就是保存一下函数对象的引用,等到了时机再翻出来调用一下。
再来看下这段代码:
//先改变状态再指定回调
new Promise((resolve, reject) => {
resolve("hello");
}).then((val) => {
console.log("val", val);
});
这段代码就是先改变状态再指定回调。没啥好说的,resolve是放在同步代码块的,所以这一个顺序也挺正常的。
关键问题三:promise.then返回的新promise的结果状态由什么决定?
1. 如果then的回调函数里没有return,那么默认返回一个resolved的promise,且结果值为undefined
2. 如果return了一个非promise的值a,那么返回一个resolved的promise,且结果值为a
3. 如果return了一个promise,那么这个promise的结果就是新promise的结果
4. 如果回调函数里抛出异常,那么返回一个rejected的promise,且结果值为抛出的东西
下面我会写几个demo来分别说明这几种情况
//没有return的情况
new Promise((resolve, reject) => {
resolve("hello");
})
.then((val) => {
console.log("val1", val);
})
.then((val) => {
console.log("val2", val);
});
//output:
//val1 hello
//val2 undefined
//return 一个非promise
new Promise((resolve, reject) => {
resolve("hello");
})
.then((val) => {
console.log("val1", val);
return "this is from then";
})
.then((val) => {
console.log("val2", val);
});
//output:
//val1 hello
//val2 this is from then
//return 一个promise
new Promise((resolve, reject) => {
resolve("hello");
})
.then((val) => {
console.log("val1", val);
return Promise.resolve("this is from promise");
})
.then((val) => {
console.log("val2", val);
});
//output:
//val1 hello
//val2 this is from promise
//抛出一个异常
new Promise((resolve, reject) => {
resolve("hello");
})
.then((val) => {
console.log("val1", val);
throw new Error("this is an error");
})
.then(
(val) => {
console.log("val2", val);
},
(reason) => {
console.log("reason", reason);
}
);
//output:
//val1 hello
//reason Error:this is an error
需要注意的是,无论是onResolved还是onRejected回调,上述规则都是适用的。
问题4:promise如何串联多个操作任务?
new Promise((resolve,reject)=>{
//先执行一个异步任务1
setTimeout(() => {
console.log('任务1');
resolve('data 1')
}, 1000);
})
.then(val=>{
//可接收任务一的结果参数
console.log('the result of task 1:',val);
//执行同步任务2
console.log('任务2');
return 'data 2'
})
.then(val=>{
//可接收任务二的结果参数
console.log('the result of task 2:',val);
//执行异步任务3
//这里要注意,是构造一个新promise并返回,而不是直接写异步任务然后在结束时机resolve
return new Promise((resolve,reject)=>{
setTimeout(() => {
console.log('任务3');
resolve('data 3')
}, 2000);
})
})
.then(val=>{
//可接收任务三的结果参数
console.log('the result of task 3:',val);
})
问题五:异常穿透是如何实现的?
首先,要使用异常穿透,就必须每个then使用默认的onRejected回调或者不写这个回调方法,如以下形式
new Promise((resolve,reject)=>{
reject('error!')
})
.then(val=>{}) //第二个onRejected回调是不写的,使用默认的回调
.then(val=>{})
.catch(err=>console.log(err))
那么默认的回调是什么呢,答案是:
err=>{ throw err }
就是把这个错误再次抛出,这样就能传递给下一个then。想想我们之前的关键问题三,then方法调用哪个回调是由新promise的状态决定的,那么如果新promise中抛出异常,自然状态是变成rejected,进而调用默认onRejected回调,于是实现了层层向下传递,直到catch方法捕获。
问题六:如何中断promise链?
在问题三罗列的规则中,如果我们返回一个具有resolved或者rejected状态的promise对象,那么接下来的then就会根据当前状态执行相应回调,但是如果我返回的是一个pending状态的promise对象呢,那么就会起到中断promise链的效果,意思就是接下来的then不执行了,无论是成功还是失败的回调。那么,如何返回一个pending状态的promise呢?很简单,如下所示:
return new Promise(()=>{}) //执行器函数里啥都不写就行了
补充问题七:new promise是同步执行还是异步执行的?
同步执行。看以下代码:
let a = 1
new Promise((resolve,reject)=>{
a = 2
})
console.log('a:',a);
//output:
//2
如果是异步执行,那么遇到new promise时会将其丢入任务队列中,继续执行主线程代码那么a应为1。
补充问题八:如果new promise的执行器函数中有异步任务,如何让异步任务完成后将结果同步到主线程中?
实际上,问题八是基于你知道问题七的基础上的。在问题七的代码中,我写的是同步任务,但如果我写的是异步任务呢?
let a = 1
new Promise((resolve,reject)=>{
setTimeout(() => {
a = 2
}, 1000);
})
console.log('a:',a);
//output:
//1
由于执行器函数中有异步代码块setTimeout,js在执行到这里时会把这里的代码丢进任务队列中,继续执行所以a没有发生改变还是1。可如果我想要a等待setTimeout完成后的结果呢?你可能会想,那还不简单么,我直接来个resolve再来个then:
let a = 1
new Promise((resolve,reject)=>{
setTimeout(() => {
a = 2
resolve(a)
}, 1000);
})
.then(val=>{
console.log('a in then:',val); //为了方便区别哪里的a,加点输出文字
})
console.log('a in main:',a);
//output:
//a in main: 1
//a in then: 2
确实,这样做你在then里可以拿到异步任务完成后的a,可是外面的a还是1呀。为啥?因为new promise的过程是同步的呀,只不过它遇到setTimeout的时候,把setTimeout任务丢进任务队列里而已,接着在then里指定成功回调,然后输出一下主线程里的当前a值。
事实上,这个问题我在我自己的一个小项目中遇到过,而且我认为这个问题还是挺重要的,在一些历史比较悠久的第三库中,有些异步API,它在设计时也允许你传入一个回调函数用于获取异步任务完成后的结果,但是API不会返回promise对象。所以如何用promise封装这些异步任务并将结果同步到主线程中,是一个至关重要的问题。而以我们目前学习到的知识点还无法解决这个问题,事实上解决这个问题我的方法需要用到async/await,所以我将会留到async/await的笔记中进行解答。