Promise的核心原理其实就是发布订阅模式,通过两个队列来缓存成功的回调(onResolve)和失败的回调(onReject)
promise的then前面的是同步代码,then后面的是异步代码
理解Promise
1.Promise的意义
新技术的出现都是为了解决旧技术的痛点。
Promise的出现,就是解决了 ES6 以前异步操作的一些技术痛点,并且使编码更加的规范(遵循promise/a+规范)。
ES6之前
处理异步的方式,大概是这样的:
function request(url, successCallback, failureCallback) {
// jQuery 的 ajax
$.ajax({
url,
success: function (res) {
successCallback(res)
},
error: function (err) {
failureCallback(err)
}
})
}
request('/api', (res) => {
console.log('成功调用此函数')
}, (err) => {
console.log('失败调用此函数')
})
上面这种写法确确实实可以解决请求函数得到后的结果,但是存在两个主要的问题:
- 对于不同的人,不同的框架设计出来的方案是不同的,没有形成一种统一的规范,那么就增加了学习成本(需要看别人实现的源码或者使用文档)。
- 可能会形成回调地狱,代码阅读性差以及难以维护。
ES6到来
ES6 的出现,带来了一种新的异步处理方式,Promise。
promise的出现,很好的解决上面的两个问题:
- 统一了编码规范。只要开发者使用 promise,就会给他一个承诺,成功的时候调用
then方法,失败的时候调用 **catch**方法等等一些列规范。 - promise的链式调用解决了回调地狱的问题,提升了代码的可读性和可维护性。
2.理解Promise的传参
ES6 中提供了一个构造函数 Promise, 也可以被称为一个类,需要通过 new 的方式来进行调用,生成一个实例对象。 构造函数 Promise 接受一个回调函数作为参数,被称为executor。该回调函数(executor) 会立即被执行,同步的表现方式。
const executor = function() {
console.log('executor函数会被立即执行')
}
const promise = new Promise(executor) // 接受一个回调函数,立即执行,同步表现形式
executor 函数也接受两个回调函数作为参数,分别取名是:resolve 和 reject。这两个回调函数是内部实现的,对于开发者而言只需要调用即可。
const executor = function(resolve, reject) {
console.log('executor函数会被立即执行')
// 成功了调用resolve
resolve()
// 失败了调用reject
reject()
}
const promise = new Promise(executor)
一般简写为
const promsie = new Promise((resolve, reject) => {
console.log('executor函数会被立即执行')
// 成功了调用resolve
resolve()
// 失败了调用reject
reject()
})
3.Promise的三种状态
Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。
- 只有操作结果,才能决定处于哪个状态,任何其他操作都无法改变这个状态。
- 一旦状态改变,就不会再变,任何时候都可以得到这个结果。
Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。
new Promise((resolve, reject) => {
resolve() // 状态:pending -> fulfilled
reject() // 状态:pending -> rejected
})
4.Promise的流程图
5.Promise的缺点
- 无法取消
Promise,一旦新建它就会立即执行,无法中途取消。 - 无法知道promise处于哪一个状态。
掌握 Promise 的重要法则
本篇主要讲解 Promise的中几个重要的方法(resolve,reject,then,catch),掌握了它们之后,promise 的棘手问题,基本上都能痛快的解决。并且下篇的 promise 的类方法都是基于这四个方法实现的,属于锦上添花,可要可不要。
1.resolve
在上篇中,介绍到executor函数接受两个参数,第一个参数被称为 resolve。
resolve函数的作用是,将Promise对象的状态从等待(pending)变为成功(fulfilled)。
resolve接受一个参数,供调用then方法的时候使用。
参数分为三种:
- 参数是一个普通的值或则对象(就是不包含下面的两种情况)
- 参数是promise
- 参数是thenable对象
情况一:普通值
不难发现,针对普通值,resolve传递什么,then就接受什么。
// 1、数字
new Promise((resolve) => {
resolve(231)
}).then(res => {
console.log(res) // 231
})
// 2、字符串
new Promise((resolve) => {
resolve('copyer')
}).then(res => {
console.log(res) // copyer
})
// 3、对象(thenable对象除外)
new Promise((resolve) => {
resolve({ name: "copyer" });
}).then((res) => {
console.log(res); // {name: 'copyer'}
});
// 4、boolean
new Promise((resolve) => {
resolve(false);
}).then((res) => {
console.log(res); // false
});
// 5、undefined
new Promise((resolve) => {
resolve(undefined);
}).then((res) => {
console.log(res); // undefined
});
// 6、null
new Promise((resolve) => {
resolve(null);
}).then((res) => {
console.log(res); // null
});
情况二: promise
当(外promise)的resolve的参数为promise对象(内promise) 的时候,那么外promise的状态是由内promise所决定的。
这里可以先使用then方法来操作下。
- 当
内promise调用了resolve方法,外promise的状态变为fulfilled,成功回调。 - 当
内promise调用了reject方法,外promise的状态变为rejected, 失败回调
// 外promise
new Promise((resolve) => {
resolve(p);
}).then(
(res) => {
console.log("成功回调:", res);
},
(err) => {
console.log("失败回调:", err);
}
);
// 内promise
const p = new Promise((resolve, reject) => {
resolve("success"); // 成功回调: success
});
const p = new Promise((resolve, reject) => {
reject("failure"); // 失败回调: failure
});
情况三:thenable方法
当传递的一个对象,对象的属性包含then方法,那么就会执行对象中then的方法。
const thenable = {
then: function () {
console.log("thenable的方法会被执行");
},
};
new Promise((resolve) => {
resolve(thenable); // 会执行对象中的then方法
}).then((res) => {
console.log("成功回调", res);
});
2.reject
executor函数接受两个参数,第二个参数被称为 reject。
reject函数的作用是,将Promise对象的状态从等待(pending)变为失败(rejected)。
reject是一个表示失败的回调函数,接受一个任意类型的参数,在then的第二个回调函数中或则catch的回调函数中,拿到失败的结果。
相对于resolve接受的不同类型的参数,表现出不同的形式; reject就相当的简单一点,reject传递过去什么,接受到的值就是什么(没有这么多的花里胡哨)。
// 方式一: catch的回调函数
new Promise((resolve, reject) => {
reject("reject不合格的原因"); // 1、传递一个不合格的原因
}).catch((err) => {
console.log(err); // reject 不合格的原因
});
// 方式二: then的第二个回调函数
new Promise((resolve, reject) => {
reject("reject不合格的原因"); // 1、传递一个不合格的原因
}).then((res) => {}, (err) => {
console.log(err) // reject不合格原因
})
3.then
then是promise对象上的一个方法(准确的来说,是在promise的原型**Promise.prototype.then**),供实例调用的。下面具体详解then的具体使用。
3.1接受两个参数
在上面的resolve,reject方法中,也提及到了 then 的大致使用,接受两个参数:
- 参数一:回调函数,接受
resolve的成功回调传递的值 - 参数二:回调函数,接受
reject的失败回调传递的值
new Promise((resolve, reject) => {
resolve("成功的值"); // 情况一
reject("失败的值"); // 情况二
}).then(
(res) => {
console.log(res); // 情况一: 成功的值
},
(err) => {
console.log(err); // 情况二: 失败的值
}
);
3.2 then的多次调用
这里的多次调用,并不是指的是链式调用(不同promise的实例),而是针对同一promise实例的then方法多次调用。 当执行resolve方法时,状态变为了fulfilled,then方法中的回调函数都会执行。(内部实现,就是then中的回调函数,存放在数组中,当状态变为fulfilled时,遍历数组,依次执行函数。)
const p = new Promise((resolve, reject) => {
resolve("copyer");
});
p.then((res) => {
console.log(res); // copyer
});
p.then((res) => {
console.log(res); // copyer
});
p.then((res) => {
console.log(res); // copyer
});
3.3 then的返回值
const p = new Promise((resolve, reject) => {
resolve("copyer");
});
p.then((res) => {
console.log(res) // copyer
}).then((res) => {
console.log(res) // undefined
})
在链式调用的时候,第二个then的res为什么是undefined?
先搞清楚一件事:
为什么可以链式调用?
then是promise原型上的方法,promise的实例可以调用。
当第二次继续调用then方法的时候,说明也是调用的promise的then方法,那就说明了两种情况:
- then的回调函数中返回了
this,promise的实例对象,可以调用then方法 - then的回调函数返回了一个新的Promise实例,也是可以调用then方法的
promise内部采用的是:第二种。因为第一种是没有意义的,针对同一个promise,后面拿取的结果跟第一次拿取的结果是一样的。
结论一:then方法的回调函数,返回一个promise
结论二:then方法的链式调用,是针对不同的promise(即每次都是一个新的promise)
知道了promise的链式调用的原理之后,就来看看返回值的类型吧。
then的返回值也是三种情况(跟 resolve 的三种情况是一样的):
- 普通的值(除了下面的两种情况)
- promise
- thenable
情况一: 普通的值
该值会被作为resolve的参数,作为下个promise的返回值。Promise.resolve(返回值)
// 普通的值
const p = new Promise((resolve, reject) => {
resolve("copyer");
});
p.then((res) => {
console.log(res) // copyer
// 函数的默认返回值: undefined ====> 被内部转化为 Promise.resolve(undefined)
}).then((res) => {
console.log(res) // undefined
})
const p = new Promise((resolve, reject) => {
resolve("copyer");
});
p.then((res) => {
console.log(res) // copyer
return {name: 'james'} // 被内部转化为 Promise.resolve({name: 'james'})
}).then((res) => {
console.log(res) // {name: 'james'}
})
情况二:promise
如果then方法是一个普通的值,会被转化成promise。如果返回值,本身就是promise,那就不需要转化了,直接根据返回的promise状态执行对应的成功回调还是失败回调。
// 成功回调
const p = new Promise((resolve, reject) => {
resolve("copyer");
});
p.then((res) => {
console.log(res) // copyer
return Promise.resolve('返回值的new_promise_success')
}).then((res) => {
console.log(res) // 返回值的new_promise_success
})
// 失败回调
const p = new Promise((resolve, reject) => {
resolve("copyer");
});
p.then((res) => {
console.log(res) // copyer
return Promise.reject('返回值的new_promise_failure')
}).then((res) => {
console.log(res) // 这里不会执行,因为返回的promise状态为rejected
}, (err) => {
console.log(err) // 返回值的new_promise_failure
})
情况三:thenable对象
const thenable = {
then: () => {
console.log("执行thenable对象中的then方法");
},
};
new Promise((resolve) => {
resolve('copyer')
}).then((res) => {
console.log(res); // copyer
return thenable;
}).then((res) => {
console.log(res); // 这里不会执行,会执行thenable对象的then方法
});
4. catch
catch是promise对象上的一个方法(准确的来说,是在promise的原型**Promise.prototype.catch ),供实例调用的。用于拿取失败**的信息。
简单使用:
new Promise((resolve, reject) => {
reject("32132");
}).catch((err) => {
console.log(err); // 32132
});
- 如果
main_promise是 rejected 状态,params_promise是 fulfilled 状态,那么 catch 捕获的是main_promsie的异常信息。 - 如果
main_promise是 fulfilled 状态,params_promise是 rejected 状态,那么 catch 捕获的是params_promsie的异常信息。 - 如果
main_promise是 rejected 状态,params_promise是 rejected 状态,那么 catch 捕获的是main_promsie的异常信息。
从而可以知道: catch的捕获规则是是**由外向内**的( 就远原则****),在依次执行代码的时候,如果遇到异常,就直捕获停止,不会向下再去捕获。
catch的返回值还是一个promise,又开启一个新的流程了。
5. finally
finally是promise对象上的一个方法(准确的来说,是在promise的原型Promise.prototype.finally),供实例调用的。 finally 是ES9(ES2018)新增的一个特性:表示Promise对象无论变成fulfilled还是rejected状态,最终都会被执行。 finally 方法是不接收参数的。 无论promise的状态是成功还是失败,都会执行该方法。返回值也是一个promise,可以链式调用
new Promise((resolve, reject) => {
resolve(p);
}).finally(() => {
// 执行
console.log(21321)
return Promise.resolve('123')
}).finally(() => {
// 执行
console.log(231321321321)
})
理解 Promise 的类方法
1.resolve
类方法resolve方法其实跟executor方法中的resolve方法是一致的,并且情况都是等价的
Promise.resolve('copyer')
// 等价于
new Promise((resolve) => {
resolve('copyer')
})
参数也分为三种情况:
- 普通的值
- promise
- thenable对象
上面提及到了,就不讲述了。
2. reject
reject方法,会将Promise对象的状态设置为rejected状态。
Promise.reject的用法相当于 new Promise,然后调用reject
Promise.reject('copyer')
// 等价于
new Promise((resolve, reject) => {
reject('copyer')
})
Promise.reject传入的参数无论时候什么类型,都会直接作为reject的参数传递到catch方法中。
3. all
将多个Promise包裹成一个新的Promise,新的Promise的状态由包裹的Promise的状态共同决定。
- 当所有的Promise的状态变为fulfilled时,新的Promise的状态为fulfilled,并且会将所有Promise的返回值组成一个数组。
- 当有一个Promise的状态为reject时,新的Promise的状态为rejected,并且会将第一个reject的返回值作为参数。
参数接受一个数组。
正常情况下的使用:promise组成一个数组
const p1 = new Promise((resolve) => {
setTimeout(() => {
resolve(10);
}, 10);
});
const p2 = new Promise((resolve) => {
setTimeout(() => {
resolve(20);
}, 20);
});
const p3 = new Promise((resolve) => {
setTimeout(() => {
resolve(30);
}, 30);
});
// p1、p2、p3组成一个数组
Promise.all([p1, p2, p3]).then((res) => {
console.log(res); // [10,20,30]
});
少数情况一:数组中包含数字,字符串等等其他类型
Promise.all([1, "copyer", p1]).then((res) => {
console.log(res); // [1, 'copyer', 10]
});
当all方法执行是,会遍历里面的数组,检查里面的item是不是promise对象,如果不是,就会转化成promise,比如
1 会被转化成 Promise.resolve(1) copyer 会被转化成 Promise.resolve('copyer')
当每项都成为 promise对象之后,再来确定新的 promise 的状态。
少数情况二:传递了一个thenable对象
const thenable = {
then: () => {
console.log(321);
},
};
Promise.all([1, thenable, p1]).then((res) => {
console.log(res);
});
会直接执行 thenable 对象中的then方法,不会执行promise中的then方法。
解释说明:正常情况是在开发中常见的,少数情况是在开发中基本上不可见的
4. allSettled
all方法有一个缺陷:当其中的一个Promise变成reject状态时,新Promise的状态也就变成了reject状态。
那对对于一些已经resolved的结果,也是拿不到的。
在ES11(ES2020)中,添加了一个新的API来解决此类问题,用法跟all基本上是一样的。
该方法会在所有的Promise都有结果(settled),无论是fulfilled,还是rejected时,都会有最终的状态,返回一个新的Promise。 并且这个Promise的结果一定是fulfilled的。
const p1 = new Promise((resolve) => {
setTimeout(() => {
resolve(10);
}, 10);
});
const p2 = new Promise((resolve) => {
setTimeout(() => {
resolve(20);
}, 20);
});
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
reject(30);
}, 30);
});
Promise.allSettled([p1, p2, p3]).then((res) => {
console.log(res);
});
返回的格式:
[
{ status: 'fulfilled', value: 10 },
{ status: 'fulfilled', value: 20 }, // 成功时value
{ status: 'rejected', reason: 30 } // 失败是reason
]
status: 表示其中Promise的状态
value: 成功的返回值
reason: 失败的返回值
5. race
如果有一个Promise有了结果,我们就希望决定最终新Promise的状态,那么可以使用race方法:
- race是竞技、竞赛的意思,表示多个Promise相互竞争,谁先有结果,那么就使用谁的结果
const p1 = new Promise((resolve) => {
setTimeout(() => {
resolve(10);
}, 10);
});
const p2 = new Promise((resolve) => {
setTimeout(() => {
resolve(20);
}, 20);
});
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(30);
}, 30);
});
Promise.race([p1, p2, p3]).then((res) => {
console.log(res); // 10
});
6. any
any方法是ES12中新增的方法,和all方法基本上相反。(还是实验性的语法,暂时没有被所有浏览器支持)。 MDN对Promise.any的解释: Promise.any() 接收一个Promise可迭代对象,只要其中的一个 promise 成功,就返回那个已经成功的 promise 。如果可迭代对象中没有一个 promise 成功(即所有的 promises 都失败/拒绝),就返回一个失败的 promise 和AggregateError类型的实例