上周学习了Promise原理,知道原理之后在自己项目中实践了一下,觉得有必要总结一下常用用法。
1、Promise.all(并行)
使用:Promise.all(array)
* Promise.all()执行返回一个promise对象
* 参数array是一个数组,数组中包含了要执行的promise对象,只有数组中的每个promise对象执行成功resolve,才会执行之后的.then操作。只要有一个promise对象执行失败reject,Promise.all()便会中止执行
* 当执行成功时,then中回调函数得到一个数组,结果依次是promise对象resolve返回的结果
下面来看一个例子:
A,B,C依次放入promise.all中,
var A = function(){
return new Promise(function(resolve,reject){
setTimeout(function(){
resolve("A")
},1000)
})
}
var B = function(){
return new Promise(function(resolve,reject){
setTimeout(function(){
resolve("B")
},2000)
})
}
var C = function(){
return new Promise(function(resolve,reject){
setTimeout(function(){
resolve("C")
},3000)
})
}
var startTime = new Date().getTime()
Promise.all([A(),B(),C()]).then(function(results){
console.log("执行完成时间:" + (new Date().getTime()-startTime)+"ms")
console.log(results)
})
// 执行完成时间:3002ms
// [ 'A', 'B', 'C' ]通过多次执行,得到执行的完成时间平均为3000ms,说明了三个函数A、B、C是并行执行。
2、Promise.race(得到一个promise对象返回的数据)
使用:Promise.race(array)
*Promise.race和all方法用法类似,不过只要array中的任一promise对象返回resolve或reject,then或者catch中的回调函数就会执行并且只得到第一个resolve或reject返回的值。(谁先执行就返回谁的数据)
*虽然then或catch的回调函数只得到最先执行的promise对象返回的resolve或reject,但是其他的promise对象依旧会继续执行,只不过我们得不到它们返回的数据。
function delayPromise(ms) {
return new Promise(function(resolve,reject){
setTimeout(function(){
resolve("timeout!")
},ms)
})
}
function normalPromise(){
return new Promise(function(resolve,reject){
setTimeout(function () {
resolve("success!")
},2000)
})
}
function settle(cb,ms){
var timeout = delayPromise(ms)
return Promise.race([cb(),timeout])
}
settle(normalPromise,1200).then(function(result){
console.log(result)
}).catch(function (e) {
console.log(e)
})
//timeout!3、按顺序执行
重复使用多个then的方法中的实现先执行A方法再执行B方法(A、B都是异步执行)
function A (){
return new Promise(function(resolve){
setTimeout(function(){
resolve(100)
},1200)
})
}
function B(){
return new Promise(function(resolve){
setTimeout(function () {
resolve(120)
},1000)
})
}
function main() {
var array = []
return A().then(function(value){
array.push(value)
return array
}).then(B).then(function (value) {
array.push(value)
return array
})
}
main().then(function (value) {
console.log(value)
}).catch(function (e) {
console.log(e)
})
// [100, 120]以上执行能满足顺序执行异步方法,但是如果方法变多,在main中的then方法就会变多,这样的代码看起来很冗长。
下面我们结合数组的遍历来改变一下main方法,数组遍历有for循环,还有Array.prototype.reduce,这里我们选择reduce。
function main(){
var array = []
var tasks = [A, B]
return tasks.reduce(function(promise,task){
return promise.then(task).then(function(value){
array.push(value)
return array
})
},Promise.resolve())
}
main().then(function (value) {
console.log(value)
}).catch(function (e) {
console.log(e)
})
// [100, 120]结果还是一样4、异常处理
* promise内部错误不影响外部函数执行
function catchError(){
return new Promise(function(resolve,reject){
throw new Error("something got error")
})
}
catchError().then(function (result) {
console.log(result)
},function (error) {
console.log(error)
})
console.log("1111")
//1111
//Error: something got error
*then的回调函数里面的错误
function errorCame(){
return new Promise(function(resolve,reject){
resolve("ok")
})
}
errorCame().then(function (result) {
console.log(result)
throw new Error("a error came")
},function (error) {
console.log(error)
})
// ok
/*回调函数中的错误未被捕获*/*捕获错误
//方式一:
function getError(){
return new Promise(function(resolve,reject){
resolve("ok")
})
}
getError().then(function (result) {
console.log(result)
throw new Error("a error came")
},function (error) {
console.log(error)
}).catch(function (e) {
console.log(e)
})
//ok
//Error: a error came
/*这里的错误被catch捕获*/
//方式二:
getError().then(function (result) {
console.log(result)
throw new Error("a error came")
},function (error) {
console.log(error)
}).then(undefined,function (error) {
console.log(error)
})
//ok
//Error: a error came5、不能断链
*每次调用then会创建一个新的Promise对象
var aPromise = new Promise(function (resolve) {
resolve(100);
});
var thenPromise = aPromise.then(function (value) {
console.log(value);
});
var catchPromise = thenPromise.catch(function (error) {
console.error(error);
});
console.log(aPromise === aPromise); // => true
console.log(aPromise !== thenPromise);// => true
console.log(thenPromise !== catchPromise);// => true
所以要注意以下错误用法:
function mistake() {
var promise = Promise.resolve(15);
promise.then(function(v) {
return v
});
return promise;
}正确用法:
function correct() {
var promise = Promise.resolve(15);
return promise.then(function(v) {
return v
});
}6、一道面试题
var prop = new Promise(function(resolve,reject){
var prop1 = new Promise(function(resolve,reject){
setTimeout(function(){
console.log("setTimeout");
resolve("setTimeout")
});
resolve("resolve promise 2");
console.log("promise2");
});
console.log("promise1");
resolve("resolve promise 1");
prop1.then(function(arg){
console.log(arg)
});
});
prop.then(function(arg){
console.log(arg);
});
console.log("end");这里的结果是:
promise2 -> promise1 -> end -> resolve promise 2 -> resolve promise 1 -> setTimeout
这里涉及到事件队列(eventloop)
为什么是这个顺序呢?
首先我们来了解一下同步任务和异步任务的执行顺序,这里有两个概念:macrotask 与 microtask
macrotask:setTimeout, setInterval, setImmediate, I/O, UI rendering
microtask:process.nextTick, Promise, MutationObserver
一个事件循环包括一个macrotask和任意个microtask。一个事件循环结束才会进行下一个事件循环,直至执行完毕。
分析以上结果:
1)整个javascript是一个macrotask,所以从上之下执行代码,promise内部是同步执行的。(promise2 -> promise1 -> end) 这是同步执行得到的结果。
2)promise.then的回调函数被推入microtask,按照先进先出原则, resolve promise 2 -> resolve promise 1。 到这里一个事件循环结束。
3)从setTimeout开始,一个新的事件循环开始,得到setTimeout。最后执行结束。
以上大致分析了事件循环,要指出的是Promise的构造函数依旧是同步执行的,其then的回调是microtask异步执行。
参考文章:
https://juejin.cn/post/6844903694069137421
http://liubin.org/promises-book/#promise-done
https://www.mattgreer.org/articles/promises-in-wicked-detail/#defining-the-promise-type
http://www.ruanyifeng.com/blog/2014/10/event-loop.html