Promise 总结
SunShinewyf opened this issue · 0 comments
最近接触到关于异步处理的一些业务,发现对异步的解决方案的用法还不是很熟悉,借此机会巩固一下关于
Promise
的用法,文章中的示例代码地址
对于PromiseA+
规范的一些讲解,这里不会涉及,如果对Promise
原理性的东西还不太了解的,可以直接移步这里
关于Promise
众所周知,js
中最原始的异步解决方案就是回调函数,但是回调函数引发的问题就是臭名昭著的callback hells
,关于异步的演化可以移步我之前比较早的这篇介绍,这里不再赘述。
Promise
是一种异步解决方案,它有resolve
和reject
两个回调参数,resolve
是Promise
从pending
状态变为fulfilled
状态后触发的回调函数。而reject
则是状态从pending
状态变为rejected
后触发的。 而且一个Promise
对象的状态只能从pending->fulfilled
或者从pending->reject
,不可能同时达到两种状态,例如:
let promise = function () {
return new Promise((resolve, reject) => {
resolve(1);
throw new Error('err');
})
}
promise().then((res) => {
console.log(res);
}).catch((err) => {
console.log(err);
})
//打印 1,而不会打印出 Error:err
上面的例子,之后打印出1而不会捕捉到抛出的异常,也就是在执行resolve(1)
的时候,Promise
的状态已经从pending
状态变为fulfilled
的了,所以此时抛出异常也不会再将状态变为rejected
。
Promise
的then()
then
是Promise
类原型对象的一个方法,它主要是为Promise
对象实例添加状态改变时的回调函数,其函数声明为:then(resolve,reject)
,resolve
参数是状态变为fulfilled
时的回调函数,reject
参数是状态变为rejected
时的回调函数,而且第二个参数reject
是可选参数。查看例子。当数字大于3的时候就会被进入reject
,否则就会进入resolve
的回调。
但是当Promise
的状态一直是pending
的时候,就会无法进入到then
的任何一个回调中.例如:
let promise = function () {
return new Promise((resolve, reject) => {
console.log('I am pending');
})
}
promise().then((res) => {
console.log('resolve', res);
}, (err) => {
console.log('reject', err);
})
上面的例子,由于Promise
实例没有从pending
进入到任何一个结果状态,所以也就不会执行then
里面的任何一个回调。
当fulfilled
的回调中不返回任何值时,在then
的第一个回调函数中就会得到一个undefined
例如:
let promise = new Promise((resolve, reject) => {
resolve('promise')
})
promise.then(function () {
//dosomething
}).then((res) => {
console.log(res)
})
//undefined
Promise
里面的catch()
catch
用于指定发生错误时的回调函数,它是then(null, rejection)
的别名,也就是:
promise.then((res) => { console.log('resolve', res) })
.catch((err) => { console.log('reject', err) });
//等同于,其中promise是一个Promise实例
promise.then((res) => { console.log('resolve', res) })
.then(null, (err) => { console.log('reject', err) });
Promise
的错误具有冒泡机制,也就是前面产生的错误,可以一直向后传递,直到被catch
捕获为止。要是最外层没有catch
用来捕获错误,错误就会冒泡到最外层,然后就会触发unhandledRejection
事件。除此之外,catch
方法返回的仍旧是一个promise
实例,所以后面可以继续接catch()
或者then()
,详见示例代码
Promise.all()
Promise.all
方法接受一个数组作为参数,数组中的值如果不是Promise
对象,就会先调用下面讲到的Promise.resolve
方法,将参数转为Promise
实例。Promise.all()
是将多个Promise
对象包装成一个Promise
实例。例如:
let promise1 = new Promise((resolve, reject) => {
resolve('promise1');
})
let promise2 = new Promise((resolve, reject) => {
resolve('promise2');
})
Promise.all([promise1, promise2]).then((res) => {
console.log('promise-all', res)
}).catch((err) => {
console.log('err', err);
})
//promise-all ['promise1','promise2'];
对于Promise.all()
返回的Promise
对象的状态,是一种与门的返回结果,也就是只有promise1
和promise2
都是fulfilled
,Promise.all()
才会变成fulfilled
状态,只要有一个是rejected
,那么它最终的状态也将是rejected
状态。
Promise.race()
和Promise.all()
类似,Promise.race()
也是接受一个数组作为参数,并且如果数组中的值类型不是Promise
对象,仍然会先调用Promise.resolve()
将数组中的值转为一个Promise
实例。
和Promise.all()
不同的是,Promise.race()
是看谁执行得快,它的最终状态也是随着最先一个实例的状态的改变而改变:
let promise1 = new Promise((resolve, reject) => {
resolve('promise1');
})
let promise2 = new Promise((resolve, reject) => {
resolve('promise2');
})
Promise.race([promise1, promise2]).then((res) => {
console.log('promise-race', res);
}).catch((err) => {
console.log('err', err);
})
// promise-race promise1
Promise
的值穿透
值穿透的场景有以下两种:
- 当
Promise
实例的状态已经是fulfilled
或者rejected
的时候,通过then
会返回this
由于then
的代码如下:
Promise.prototype.then = function (onFulfilled, onRejected) {
if (!isFunction(onFulfilled) && this.state === FULFILLED ||
!isFunction(onRejected) && this.state === REJECTED) {
return this;
}
...
};
实例讲解:
const func1 = function () {
return new Promise((resolve, reject) => {
resolve('promise1');
})
}
const func2 = function () {
return new Promise((resolve, reject) => {
resolve('promise2');
})
}
func1().then(func2()).then((res) => {
console.log(res)
})
//promise1
在第一个then
中由于传入了一个promise
类型(非Function),所以func2()
的Promise
不会传递到后面,而此时promise
的状态已经是fulfilled
了,从而打印出func1()
中的resolve
的值,
当改成如下这样就可以了:
const func1 = function () {
return new Promise((resolve, reject) => {
resolve('promise1');
})
}
const func2 = function () {
return new Promise((resolve, reject) => {
resolve('promise2');
})
}
func1().then(func2).then((res) => {
console.log(res)
})
- 由父
Promise
衍生出的子Promise
,并且当子Promise
实例状态为pending
时,子Promise
的状态由父Promise
设定:
let promise1 = new Promise((resolve, reject) => {
resolve('promise1');
})
let promise2 = promise1.then();
promise2.then((res) => {
console.log(res)
})
//promise1
这种情况其实是上面情况的一种变种,不解释。
总结:.then 或者 .catch 的参数期望是函数,传入非函数则会发生值穿透。
Promise
实践
可知长度的关联串行
这种场景类似于:taskB
要依靠taskA
的数据去请求,taskC
又要依靠taskB
返回的数据来请求数据,这种是典型的关联串行的模式,但是又是可知长度的,这种可以直接采取下面这种模式:
taskA().then((res1) => {
return taskB(res1)
}).then((res2) => {
return taskC(res2)
}).then((res3) => {
console.log(res3);
})
其中`taskA,taskB,taskC`的结构都是类似如下:
let taskA = new Promise((resolve, reject) => {
rp(urlA).then((res) => {
resolve(res)
})
})
在这种情况下,在then()
中都需要返回一个Promise
实例,否则会出现掉链
未知长度的关联串行
场景示例:在请求某个接口数据时,需要分页去请求,传递offset
值和count
值去请求,每次请求返回的结果是数据以及nextOffset
,这种情况下,就是判断最终这个nextOffset
是否为-1来终止循环次数,从而形成了一种有关联的但是是未知长度的串行请求。
function eachTask(offset) {
let option = {
url: 'yourUrl',
offset: offset,
count: 30
}
return new Promise((resolve, reject) => {
rp(option).then(function (res) {
resolve(res);
})
})
}
let list = [];
const allTask = (offset = 0) => eachTask(offset).then(res => {
res && (list = list.concat(res.data));
if (res.nextOffset && res.nextOffset != -1) {
return eachTask(res.nextOffset);
} else {
return Promise.resolve(list);
}
})
allTask(0).then(res => {
console.log(res)
}).catch((err) => {
console.log('err', err);
})
并行请求
使用Promise
实现并行请求,需要使用到Promsie.all()
,处理方式类似于下面这种:
getPromiseArr()
.then((arr) => {
return Promise.all(arr);
})
.then((res) => {
console.log(res);
}).catch((err) => {
console.log('err', err);
})
其中getPromiseArr()
可以返回一个Promise
类型的数组。
基于对Promise
用法不太熟悉的基础上,整理出以上内容,有觉得不太正确的地方的可以一起交流~
上文中的示例代码地址