Promise概述
概念
Promise是异步编程的一种解决方案,可以替代传统的解决方案--回调函数和事件。ES6统一了用法,并原生提供了Promise对象。作为对象,Promise有以下两个特点:
特点
(1)对象的状态不受外界影响。
(2)一旦状态改变了就不会再变,也就是说任何时候Promise都只有一种状态。
状态
Promise的实例有三个状态 :
- Pending(进行中)
- Resolved(已完成)
- Rejected(已拒绝)
把一件事情交给promise时,它的状态就是Pending,任务完成了状态就变成了Resolved、没有完成失败了就变成了Rejected
常用方法
then
then中一般传入两个参数(函数),第一个对应resolve成功的回调,第二个对应reject失败的回调
then的第二个参数就等同于catch方法,但是需要注意
- Promise内部报错,reject抛出错误后,then的第二个参数和catch方法都存在的情况下,只有then的第二个参数能捕获到,如果then的第二个参数不存在,catch方法会捕捉到
- catch不仅捕捉promise中抛出的错误,还会捕捉前面then中的错误
catch
catch:捕捉promise错误函数,和then的第二个参数(函数)作用一样,处理错误,由于Promise抛出错误具有冒泡性质,能够不断传递,会传到catch中,所以一般来说所有错误处理放在catch中,then中只处理成功的,同时catch还会捕捉then中第一个参数(函数)抛出的异常
finally
finally 方法没有参数,也不会改变 Promise 的状态,它只是在 Promise 结束时提供了一个通知机制,让我们可以在 Promise 结束后执行一些清理工作(比如操作文件的时候关闭文件流)。
all
接受一个具有Iterable接口的类型,如数组,Map,Set,传入多个promise,
每一个promise执行成功resolve,最后才执行成功(返回一个Promise实例),进入then,否则失败进入catch
allSettled
allSettled:接受一个具有Iterable接口的类型,如数组,Map,Set,传入多个promise,
每个promise状态改变成fulfilled或者rejected之后返回,返回的是一个数组对象,对象中有状态status和每一项的返回结果value
race
race:以快为准,数组中所有的promise对象,有一个先执行了何种状态,该对象就为何种状态,并执行相应函数 接受一个具有Iterable接口的类型,如数组,Map,Set,传入多个promise, 其中有一个promise返回了,不管是fulfilled或者rejected,直接返回这个promise的结果
any
any: 接受一个具有Iterable接口的类型,如数组,Map,Set,传入多个promise,
当传入的任何一个promise成功的时候,不管其他是否成功或者失败,会把成功的那个promise返回
代码实现
const PENDING = 'pending'
const RESOLVED = 'resolved'
const REJECTED = 'rejected'
function myPromise(fn){
this.state = PENDING;
this.value = null;
this.resolveCallbacks = [];
this.rejectedCallbacks = [];
function resolve(value){
this.state = RESOLVED;
this.value = value;
this.resolveCallbacks.map(cb => cb(this.value))
}
function reject(value){
this.state = REJECTED;
this.value = value;
this.rejectedCallbacks.map(cb => cb(this.value))
}
try{
fn(resolve,reject)
}catch(e){
reject(e)
}
myPromise.prototype.then = function(onFulfilled,onRejected){
const that = this;
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled:v =>{}
onRejected = typeof onRejected === 'function' ? onRejected:v =>{}
if(that.state === PENDING){
this.resolveCallbacks.push(onFulfilled)
this.rejectedCallbacks.push(onRejected)
}
if(that.state === RESOLVED){
onFulfilled(that.value)
}
if(that.state === REJECTED){
onRejected(that.value)
}
return that;
}
}
全部代码【含常用方法】
class MyPromise {
static PENDING = "等待";
static FULFILLED = "成功";
static REJECTED = "失败";
constructor(func) {
this.status = MyPromise.PENDING; // 定义状态
this.result = null; // 定义返回结果
this.resloveCallbacks = []; // 存放reslove回调函数的数组
this.rejectCallbacks = []; // 存放reject回调函数的数组
// 当Promise中抛出错误时,会把promise的状态改为失败并且将错误设置为结果
try {
func(this.reslove.bind(this), this.reject.bind(this)); // 执行传入的函数
} catch (err) {
this.reject(err);
}
}
reslove(result) {
// resolve是在事件循环末尾执行的,所以这里用setTimeout包裹一下
setTimeout(() => {
this.status = MyPromise.FULFILLED; // 更改状态
this.result = result;
// 遍历执行PENDING状态时保存的reslove回调函数
this.resloveCallbacks.forEach((callback) => {
callback(result);
});
});
}
reject(result) {
// reject同样用setTimeout包裹一下
setTimeout(() => {
this.status = MyPromise.FULFILLED; // 更改状态
this.result = result;
// 遍历执行PENDING状态时保存的reject回调函数
this.rejectCallbacks.forEach((callback) => {
callback(result);
});
});
}
then(onFULFILLED, onREJECTED) {
let newPromise = new MyPromise((resolve, reject) => {
// then中两个参数可以传入undefined,做下处理
onFULFILLED =
typeof onFULFILLED === "function"
? onFULFILLED
: (value) => {
value;
};
onREJECTED =
typeof onREJECTED === "function"
? onREJECTED
: (reason) => {
throw reason;
};
if (this.status === MyPromise.PENDING) {
// PENDING状态时
this.resloveCallbacks.push(() => {
try {
let nextResult = onFULFILLED(this.result);
resolvePromise(newPromise, nextResult, resolve, reject);
} catch (err) {
reject(err);
}
});
this.rejectCallbacks.push(() => {
try {
let nextResult = onREJECTED(this.result);
resolvePromise(newPromise, nextResult, resolve, reject);
} catch (err) {
reject(err);
}
});
}
if (this.status === MyPromise.FULFILLED) {
setTimeout(() => {
try {
// 执行成功后的回调
let nextResult = onFULFILLED(this.result);
resolvePromise(newPromise, nextResult, resolve, reject);
} catch (err) {
reject(err);
}
});
}
if (this.status === MyPromise.REJECTED) {
setTimeout(() => {
try {
// 执行失败后的回调
let nextResult = onREJECTED(this.result);
resolvePromise(newPromise, nextResult, resolve, reject);
} catch (err) {
reject(err);
}
});
}
});
return newPromise;
}
catch(onREJECTED) {
return this.then(null, onREJECTED);
}
finally() {
return this.then(undefined, undefined);
}
static resolve(value) {
return new MyPromise((resolve) => {
resolve(value);
});
}
static reject(reason) {
return new MyPromise((resolve, reject) => {
reject(reason);
});
}
static all(promises) {
return new MyPromise((resolve, reject) => {
// 创建一个空数组results,用于存储每个Promise的 resolve 结果
const results = [];
let count = 0;
const processResult = (index, result) => {
// 结果存入results数组(具有Iterator接口的对象)中,并更新count变量
results[index] = result;
count++;
if (count === promises.length) {
// 如果count等于Promise 数组(具有Iterator接口的对象)的长度,则说明所有Promise都resolve了,此时调用 resolve 方法
resolve(results);
}
};
// 遍历传入的Promise数组(具有 Iterator 接口的对象),对每个Promise调用then方法
for (const [index, promise] of [...promises].entries()) {
Promise.resolve(promise).then((result) => {
processResult(index, result);
}, reject);
}
});
}
}
function resolvePromise(newPromise, x, resolve, reject) {
if (x === newPromise) {
// 因为x是回调的结果值,如果x指向newPromise即自己,那么会重新解析自己,导致循环调用
throw new TypeError("禁止循环调用");
}
// 如果x是一个Promise,我们必须等它完成(失败或成功)后得到一个普通值时,才能继续执行。
// 那我们把要执行的任务放在x.then()的成功回调和失败回调里面即可
// 这就表示x完成后就会调用我们的代码。
// 但是对于成功的情况,我们还需要再考虑下,x.then成功回调函数的参数,我们称为y
// 那y也可能是一个thenable对象或者promise
// 所以如果成功时,执行resolvePromise(promise2, y, resolve, reject)
// 并且传入resolve, reject,当解析到普通值时就resolve出去,反之继续解析
// 这样子用于保证最后resolve的结果一定是一个非promise类型的参数
if (x instanceof MyPromise) {
x.then(
(y) => {
resolvePromise(newPromise, y, resolve, reject);
},
(r) => reject(r)
);
}
// (x instanceof myPromise) 处理了promise的情况,但是很多时候交互的promise可能不是原生的
// 就像我们现在写的一个myPromise一样,这种有then方法的对象或函数我们称为thenable。
// 因此我们需要处理thenable。
else if ((typeof x === "object" || typeof x === "function") && x !== null) {
// 暂存x这个对象或函数的then,x也可能没有then,那then就会得到一个undefined
try {
var then = x.then;
} catch (e) {
// 如果读取then的过程中出现异常则reject异常出去
return reject(e);
}
// 判断then是否函数且存在,如果函数且存在那这个就是合理的thenable,我们要尝试去解析
if (typeof then === "function") {
// 状态只能更新一次使用一个called防止反复调用
// 因为成功和失败的回调只能执行其中之一
let called = false;
try {
then.call(
x,
(y) => {
// called就是用于防止成功和失败被同时执行,因为这个是thenable,不是promise
// 需要做限制如果newPromise已经成功或失败了,则不会再处理了
if (called) return;
called = true;
resolvePromise(newPromise, y, resolve, reject);
},
(r) => {
// called就是用于防止成功和失败被同时执行,因为这个是thenable,不是promise
// 需要做限制如果newPromise已经成功或失败了,则不会再处理了
if (called) return;
called = true;
reject(r);
}
);
// 上面那一步等价于,即这里把thenable当作类似于promise的对象去执行then操作
// x.then(
// (y) => {
// if (called) return;
// called = true;
// resolvePromise(newPromise, y, resolve, reject);
// },
// (r) => {
// if (called) return;
// called = true;
// reject(r);
// }
// )
} catch (e) {
// called就是用于防止成功和失败被同时执行,因为这个是thenable,不是promise
// 需要做限制如果newPromise已经成功或失败了,则不会再处理了
if (called) return;
called = true;
reject(e);
}
} else {
// 如果是对象或函数但不是thenable(即没有正确的then属性)
// 当成普通值则直接resolve出去
resolve(x);
}
} else {
return resolve(x);
}
}
优缺点
缺点
- 顺序错误处理:Promise链中的错误很容易被无意中默默忽略掉。
- 单一值:Promise只能有一个完成值或一个拒绝理由
- 单决议:Promise只能被决议一次
- 惯性:运动状态(使用回调)的代码库会一直保持运动状态(使用回调),直到受到一位聪明的,理解Promise开发者的作用。
- 无法取消的Promise:一旦创建,如果出现了某一种情况使得这个任务悬而未决的话,你也没办法从外部停止他的进程。
- Promise性能:稍慢一点,但是作为交换,你得到的是大量内建的可信任性,对Zalgo的避免以及可组合性。
一些特殊情况
值穿透
.then .catch 期望是函数, 如果传入非函数则会发生值穿透
promise的data 就会保存上一个promise.data
Promise.resolve(1)
.then(2)
.then(Promise.resolve(3))
.then(console.log) // 1
链式调用-不同返回值
如果返回的是普通对象,将作为下次then函数的参数传入;
如果返回的是Promise对象(p2),那么p2将会接管以后的Promise链
错误处理
对于多数开发者来说,错误处理最自然地形式就是同步的try…catch结构。遗憾的是,他只能是同步的,无法用于异步代码模式。
function foo(){
setTimeout(function(){
baz.bar();
},100)
}
try{
foo() //后面的baz.bar()会抛出全局错误
}
catch{
//永远不会到达这里
}
在回调中,一些模式化的错误处理方式已经出现,最值得一提的是error-first回调风格:
function foo(cb){
setTimeout(function(){
try{
var x = baz.bar();
cb(null,x);//成功
}
catch(err){
cb(err)
}
},100)
}
foo(function(err,val){
if(err){
console.error(err);
}else{
console.log(val);
}
})
只有在baz.bar()调用同步地立即执行的情况下才有用,否则如果baz.bar()本身有自己的异步完成函数,其中的任何错误都不能被捕捉。