一、Promise 的理解和使用
1.1 Promise 是什么?
1.1.1 理解
1.抽象表达:
1)Promise 是一门新的技术(基于ES6规范)
2)Promise 是 JS 中进行异步编程的新解决方案
ps:旧方案是单纯使用回调函数
2.具体表达:
1)从语法上来说:Promise 是一个构造函数
2)从功能上来说:Promise 对象用来封装一个异步操作并可以获取其成功/失败的结果值
1.1.2 Promise 的状态改变
pending 变为 resolved 或
变为 rejected
ps:只有这两种状态,且一个 Promise 对象只能改变一次,无论变为成功还是失败,都有一个结果数据 => 成功称为 value , 失败称为 reason。
1.1.3 Promise 的基本流程
图源尚硅谷
1.1.4 Promise 的基本使用
1.基本代码
// 创建 promise 对象(pending 状态), 指定执行器函数
const p = new Promise((resolve,reject) =>{
// 在执行器中启动异步任务
setTimeout(() => {
const time = Date.now()
if(time % 2 === 1){
resolve('成功的值' + time) //若成功,调用resolve(), 指定成功的 value 并且变成 resolved 状态
}else{
reject('失败的值' + time) //若失败,调用reject(), 指定成功的 reason 并且变成 rejected 状态
}
},1000)
})
p.then(value =>{
console.log(value)
},reason =>{
console.log(reason)
})
2.使用 Promise 封装基于定时器的异步
function doDelay(time) {
// 创建 Promise 对象
return new Promise((reslove, reject) => {
// 启动异步任务
console.log('启动异步任务')
setTimeout(() => {
console.log('延迟任务开始执行')
const time = Date.now()
if (time % 2 === 1) {
resolve('成功的数据' + time)
} else {
reject('失败的数据' + time)
}
}, time);
});
}
const Promise = doDelay(2000)
Promise.then(value => {
console.log(value)
}, reason => {
console.log(reason)
})
3.使用 promise 封装 ajax 异步请求
function sendAjax(url) {
return new Promise((reslove, reject) => {
let xhr = new XMLHttpRequest()
xhr.open('GET', rul)
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
reslove(xhr.response)
} else {
reject(xhr.status)
}
}
}
xhr.send
});
}
sendAjax('http://localhost:3000/posts')
.then(value => {
console.log(value)
}, reason => {
console.log(reason)
})
1.2 为什么要用 Promise ?
1.2.1 指定回调函数的方式更加灵活
1.旧方法:必须在启动异步任务之前指定
2.Promise:启动异步任务 => 返回 Promise 对象 => 给 Promise 对象绑定回调函数(一个或多个)
1.2.2 支持链式调用,可以解决 回调地狱 问题
1.什么是回调地狱 ?
回调函数嵌套调用,外部回调函数异步执行的结果是嵌套的回调执行的条件
2.回调地狱的缺点?
不便于阅读
不便于异常处理
3.解决方案?
Promise 链式调用
4.终极解决方案?
async/await
1.3 如何使用 Promise ?
1.3.1 API
1.Promise 构造函数:Promise(executor){}
1)excutor 函数:执行器(resolve,reject)=> {}
2)resolve 函数:内部定义成功时调用的函数 value => {}
3)reject 函数:内部定义失败时调用的函数 reason => {}
ps:executor 会在 Promise 内部立即同步调用,在执行器中执行异步操作
2.Promise.prototype.then 方法:(onResolved,onRejected) => {}
1)onResolved 函数:成功的回调函数 (value) => {}
2)onRejected 函数:失败的回调函数 (reason) => {}
ps:指定用于得到成功 value 的成功回调和用于失败 reason 的失败回调,返回一个新的 Promise 对象
3.Promise.prototype.catch 方法:(onRejected) => {}
onRejected 函数:失败的回调函数 (reasone)=> {}
ps:返回一个 promise 对象,并且处理拒绝的情况。它的行为与调用 Promise.prototype.then(undefined, onRejected) 相同
4.Promise.resolve 方法 (value) => {}
value:成功的数据或 promise 对象
ps:返回一个成功/失败(被解析过)的 promise 对象
5.Promise.reject 方法 (reason)=> {}
reason:失败的原因
ps:返回一个失败的 promise 对象
6.Promise.all 方法 (promises) => {}
promises:包含n个 promise 的数组
ps:返回一个新的 promise,只有所有的 promise 都成功才成功,只要有一个失败即为失败
7.Promise.race 方法 (promises) => {}
promises:包含n个 promise 的数组
ps:返回一个新的 promise ,第一个完成的 promise 的结果状态就是最终的结果状态
1.3.2 Promise 的几个关键问题(重点)
1.如何改变 promise 的状态?
1)resolve(value):如果当前是 pending 就会变为 resolved
2)reject(reason):如果当前是 pending 就会变为 rejected
3)抛出异常:如果当前是 pending 就会变为 rejected
2.一个 promise 指定多个成功/失败回调函数都会调用吗?
当 promise 改变为对应状态时都会调用
3.改变 promise 状态和指定回调函数的先后顺序?
1)都有可能,正常情况下时先指定回调再改变状态,但也可以改变状态再指定回调
2)如何先改变状态,再指定回调?
①在执行器中直接调用 resolve()/ reject()
//先改状态, 后指定回调函数
new Promise((resolve, reject) => {
resolve(1) // 先改变的状态(同时指定数据)
}).then(// 后指定回调函数, 异步执行回调函数
value => {console.log('value2', value)},
reason => {console.log('reason2', reason)}
)
②延迟更长时间才调用 then():此时异步执行
// 常规: 先指定回调函数, 后改变的状态
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1) // 后改变的状态(同时指定数据), 异步执行回调函数
}, 1000);
}).then(// 先指定回调函数, 保存当前指定的回调函数
value => {},
reason => {console.log('reason', reason)}
)
3)什么时候才能得到数据?
① 如果先指定的回调,那当状态发生改变时,回调函数就会调用,得到数据
② 如果先改变状态,则当指定回调时,回调函数被调用,得到数据
4.promise.then() 返回的新 promise 的结果状态由什么决定?
1)简单表达:由 then()指定的回调函数执行结果决定
2)详细表达:
① 如果抛出异常,新 promise 变为 rejected,reason为抛出的异常
② 如果返回的是非 promise 的任意值,新 promise 变为 resolved ,value 为返回的值
③ 如果返回的是另一个新 promise,此 promise 的结果就会成为新 promise 的结果
5.promise 中如何串联多个操作任务?
1)promise 的 then()返回一个新的 promise ,可以变成 then()的链式调用
2)通过 then()的链式调用串联多个同步/异步任务
6.promise 异常穿透?
1)当使用 promise 的 then()链式调用时,可以在最后指定失败的回调
2)前面任何操作出了异常,都会传到最后失败的回调中处理
7. 如何中断 promise 链?【面试考点】
(注意这里是中断而不是终止,因为 Promise 无法终止,这里的意思是:在合适的时候,把 pending 状态的 promise 给 reject 掉。)
1) 当使用 promise 的 then 链式调用时, 在中间中断, 不再调用后面的回调函数
2) 办法: 在回调函数中返回一个 pendding 状态的 promise 对象
【原因】:因为执行then中的成功还是失败的回调,是由上一个promise的状态决定的,如果中间返回一个初始状态的promise对象,then方法不知道执行哪一个回调,后面的then方法也不会执行,promisethen链会中断。
二、手写 Promise
声明 Promise
首先,promise 是一个类,所以用class来声明
new Promise((resolve, reject)=>{})
, 因此要传入一个参数(函数),叫做executor- executor 里面有两个参数,resolve 和 rejecte ,表示成功/失败
class Promise{
// 构造器
constructor(executor){
// 成功
let resolve = () => {};
// 失败
let reject = () => {};
// 立即执行
executor(resolve,reject)
}
}
解决基本状态
官方对Promise有如下规定:
- Promise 存在三个状态(state):pending、fulfilled、rejected;
- pending(等待态)为初始态,并可以转化为 fulfilled(成功态)和rejected(失败态);
- 成功时,不可转为其他状态,并且需要有一个不可改变的值(value);
- 失败时,不可转为其他状态,并且需要有一个不可改变的原因(reason);
new Promise((resolve, reject)=>{resolve(value)})
resolve为成功,接收value参数,并且改变状态为fulfilled,不可以再次改变;new Promise((resolve, reject)=>{reject(reason)}) reject
为成功,接收reason参数,并且改变状态为rejected,不可以再次改变;- 若 executor 函数报错,直接执行reject();
因此得到以下代码:
class Promise {
constructor(executor) {
// 初始化state为等待态
this.state = 'pending';
//成功的值
this.value = undefined;
//失败的原因
this.reason = undefined;
let resolve = value => {
// state 改变,resolve 调用就会失败
if (this.state === 'pending') {
// resolve调用后,state转化为成功态
this.state = 'fulfilled';
// 储存成功的值
this.value = value;
}
}
let reject = reason => {
// state 改变,reject 调用就会失败
if (this.state === 'pending') {
// resolve调用后,state转化为成功态
this.state = 'rejected';
// 储存成功的值
this.reason = reason;
}
}
// 若 executor 执行报错,则直接调用 reject
try{
executor(resolve,reject);
}catch(err){
reject(err);
}
}
}
then方法
promise 有一个叫做 then 的方法,里面有两个参数:onFulfilled,onRejected,成功返回值,失败返回原因
- 当state状态为fulfilled,则执行onFulfilled,传入this.value;
- 当state状态为rejected,则执行onRejected,传入this.reason;
- 因此把onFulfilled、onRejected看作函数,则必须分别在fulfilled,rejected之后被调用,value或reason依次作为他们的第一个参数
class Promse {
constructor(executor) {... }
// then方法,有两个参数 onFulfilled 和 onRejected
then(onFulfilled, onRejected) {
// 状态为fulfilled,执行onFulfilled,传入成功的值
if (this.state === 'fulfilled') {
onFulfilled(this.value)
}
// 状态为rejected,执行onRejected,传入失败的原因
if (this.state === 'rejected') {
onRejected(this.reason)
}
}
}
功能已经基本实现,但无法对付setTimeout
解决异步实现
现在可以基本实现简单的同步代码,但是当resolve在setTimeout内执行,then这时的state还是pending的状态,所以我们需要在then调用的时候,将失败和成功存到各自的数组,一旦reject或者resolve,就调用他们
类似订阅发布,先将then里面的两个函数储存起来,由于一个promise可以有多个then,所以存在同一个数组内
// 解决异步调用
class Promise {
constructor(executor){
this.state = 'pending';
this.value = undefined;
this.reason = undefined;
// 成功存放的数组
this.onResolvedCallbacks = [];
// 失败存放的数组
this.onRejectedCallbacks = [];
let resolve = value =>{
if(this.state === 'pending'){
this.state = 'fulfilled'
this.value = value
// 一旦resolve执行,调用成功数组的函数
this.onResolvedCallbacks.forEach(fn => fn())
}
}
let reject = reson => {
if(this.state === 'padding'){
this.state = 'fulfilled'
this.reason = reson
// 一旦reject执行,调用失败数组的函数
this.onRejectedCallbacks.forEach(fn => fn())
}
}
then(onFulfilled,onRejected){
if(this.state === 'fulfilled'){
onFulfilled(this.value)
};
if(this.state === 'rejected'){
onRejected(this.reason)
}
// 当状态state为pending时
if(this.state === 'pending'){
// onFulfilled 传入到成功数组
this.onResolvedCallbacks.push(() => {
onFulfilled(this.value)
})
// onRejected 传入到失败数组
this.onRejectedCallbacks.push(() => {
onRejected(this.reason)
})
}
}
}
}
解决链式调用
我们常常用到 new Promise().then().then()
,这就是链式调用,解决回调地狱问题
1.为了达成链式,我们默认在第一个then里返回一个promise。官方规定了一个方法,在then里面返回一个新的promise,称为promise2:promise2 = new Promise(resolve,reject) => {}
- 将这个promise2返回的值传递到下一个then中
- 如果返回一个普通的值,则将普通的值传递给下一个then中
2.当我们在第一个then中 return 了一个参数(需判断)。这个return出来的新promise就是onFulfilled()或onRejected()的值
则规定onFulfilled()或onRejected()的值,即第一个then返回的值,叫做x,判断x的函数叫做resolvePromise
- 首先,要看x是不是promise
- 如果是promise,则取他的结果,作为新promise2成功的结果
- 如果是普通值,直接作为promise2的成功结果
- 所以比较x和promise2
- resolvePromise的参数有promise2(默认返回的promise),x(我们自己return的对象),resolve,reject
- resolve和reject是promise2 的
class Promise {
constructor(executor) {
this.state = 'pending';
this.value = undefined;
this.reason = undefined;
// 成功存放的数组
this.onResolvedCallbacks = [];
// 失败存放的数组
this.onRejectedCallbacks = [];
let resolve = value => {
if (this.state === 'pending') {
this.state = 'fulfilled'
this.value = value
this.onResolvedCallbacks.forEach(fn => fn())
}
}
let reject = reson => {
if (this.state === 'padding') {
this.state = 'fulfilled'
this.reason = reson
this.onRejectedCallbacks.forEach(fn => fn())
}
}
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
then(onFulfilled, onRejected) {
// 声明返回的promise2
let promise2 = new Promise((reslove, reject) => {
if (this.state === 'fulfilled') {
let x = onFulfilled(this.value)
// resolvePromise函数,处理自己return的promise和默认promise2的关系
resolvePromise(promise2, x, reslove, reject)
};
if (this.state === 'rejected') {
let x = onRejected(this.reason)
resolvePromise(promise2, x, reslove, reject)
}
// 当状态state为pending时
if (this.state === 'pending') {
this.onResolvedCallbacks.push(() => {
let x = onFulfilled(this.value)
resolvePromise(promise2, x, reslove, reject)
})
this.onRejectedCallbacks.push(() => {
let x = onRejected(this.reason)
resolvePromise(promise2, x, reslove, reject)
})
}
});
// 返回promise,完成链式
return promise2;
}
}
完成resolvePromise函数
如果x === promise2,则会造成循环引用,自己等待自己完成,则报“循环引用”错误
let p = new Promise(resolve => {
resolve(0);
});
var p2 = p.then(data => {
// 自己等待自己完成,无限循环
return p2;
})
1.判断x
- Otherwise,if x is an object or function,Let then be x.then
- x 不能是 null
- x 是普通函数,直接resolve(x)
- x 是对象或者函数(包括promise),let then = x.then
- 声明了then
- 如果取then报错,则走reject()
- 如果then是个函数,则用 call 执行 then,第一个参数是this,后面是成功的回调和失败的回调
- 如果成功的回调还是 promise,就递归继续解析
- 失败和成功只能调用一个,所以设定一个called防止多次调用
// 完成resolvePromise函数
function resolvePromise(promise2, x, resolve, reject) {
// 循环引用报错
if (x === promise2) {
return reject(new TypeError('Chaining cycle detected for promise'))
}
// 防止多次调用
let called;
// x不是null,且x是对象或者函数
if (x != null && (typeof x === 'object' || typeof x === 'function')) {
try {
// A+ 规定,声明then = x 的then方法
let then = x.then;
// 如果then是函数,则默认是promise
if (typeof then === 'function') {
// 就让then 执行,第一个参数是this,后面是成功回调 和 失败回调
then.call(x, y => {
// 失败和成功只能调用一个
if (called) return;
called = true;
// resolve的结果依旧是 promise 那就继续解析
resolvePromise(promise2, x, resolve, reject);
}, err => {
// 成功和失败只能调用一个
if (called) return;
called = true;
reject(err); // 失败了就失败了
})
} else {
resolve(x); // 直接成功即可
}
} catch (e) {
// 也属于失败
if (called) return;
// 取then出错了那就不继续执行
reject(e)
}
} else {
resolve(x)
}
}
解决其他问题
1.规定onFulfilled,onRejected都是可选参数,如果他们不是函数,必须被忽略
- onFulfilled返回一个普通的值,成功时直接等于 value => value
- onRejected返回一个普通的值,失败时如果直接等于 value => value,则会跑到下一个then中的onFulfilled中,所以直接扔出一个错误 reason = throw err
2.规定onFulfilled或onRejected 不能同步被调用,必须异步调用,我们用 setTimeout解决异步问题
- 如果onFulfilled 或 onRejected 报错,则直接返回 reject()
// 解决其他问题 class Promise { constructor(executor) { this.state = 'pending'; this.value = undefined; this.reason = undefined; this.onResolvedCallbacks = []; this.onRejectedCallbacks = []; let resolve = value => { if (this.state === 'pending') { this.state = 'fulfilled' this.value = value this.onResolvedCallbacks.forEach(fn => fn()) } } let reject = reson => { if (this.state === 'padding') { this.state = 'fulfilled' this.reason = reson this.onRejectedCallbacks.forEach(fn => fn()) } } try { executor(resolve, reject); } catch (err) { reject(err); } } then(onFulfilled, onRejected) { // onFulfilled 不是函数,就忽略onFulfilled,直接返回 value onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value // onRejected 如果不是函数,就忽略onRejected,直接扔出错误 onRejected = typeof onRejected === 'function' ? onRejected : err => {throw err} let promise2 = new Promise((resolve,reject) => { if(this.state === 'function'){ // 异步 setTimeout(() => { try{ let x = onFulfilled(this.value) resolvePromise(promise2,x,resolve,reject) }catch(e){ reject(e) } },0) } if(this.state === 'rejected'){ // 异步 setTimeout(() => { // 如果报错 try{ let x = onRejected(this.reason) resolvePromise(promise2,x,resolve,reject) }catch(e){ reject(e) } }, 0); } if(this.state === 'pending'){ this.onRejectedCallbacks.push(() => { // 异步 setTimeout(() => { try{ let x = onFulfilled(this.value) resolvePromise(promise2,x,resolve,reject) }catch(e){ reject(e) } }, 0); }) this.onRejectedCallbacks.push(() => { // 异步 setTimeout(() => { try{ let x = onRejected(this.reason) resolvePromise(promise2,x,resolve,reject) }catch(e){ reject(e) } }, 0); }) } }) // 返回 promise 完成链式 return promise2 } }
大功告成
最后加上catch,resolve,reject,race,all 方法
class Promise{
constructor(executor){
this.state = 'pending';
this.value = undefined;
this.reason = undefined;
this.onResolvedCallbacks = [];
this.onRejectedCallbacks = [];
let resolve = value => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
this.onResolvedCallbacks.forEach(fn=>fn());
}
};
let reject = reason => {
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = reason;
this.onRejectedCallbacks.forEach(fn=>fn());
}
};
try{
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
then(onFulfilled,onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
let promise2 = new Promise((resolve, reject) => {
if (this.state === 'fulfilled') {
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
};
if (this.state === 'rejected') {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
};
if (this.state === 'pending') {
this.onResolvedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0)
});
};
});
return promise2;
}
catch(fn){
return this.then(null,fn);
}
}
function resolvePromise(promise2, x, resolve, reject){
if(x === promise2){
return reject(new TypeError('Chaining cycle detected for promise'));
}
let called;
if (x != null && (typeof x === 'object' || typeof x === 'function')) {
try {
let then = x.then;
if (typeof then === 'function') {
then.call(x, y => {
if(called)return;
called = true;
resolvePromise(promise2, y, resolve, reject);
}, err => {
if(called)return;
called = true;
reject(err);
})
} else {
resolve(x);
}
} catch (e) {
if(called)return;
called = true;
reject(e);
}
} else {
resolve(x);
}
}
//resolve方法
Promise.resolve = function(val){
return new Promise((resolve,reject)=>{
resolve(val)
});
}
//reject方法
Promise.reject = function(val){
return new Promise((resolve,reject)=>{
reject(val)
});
}
//race方法
Promise.race = function(promises){
return new Promise((resolve,reject)=>{
for(let i=0;i<promises.length;i++){
promises[i].then(resolve,reject)
};
})
}
//all方法(获取所有的promise,都执行then,把结果放到数组,一起返回)
Promise.all = function(promises){
let arr = [];
let i = 0;
function processData(index,data){
arr[index] = data;
i++;
if(i == promises.length){
resolve(arr);
};
};
return new Promise((resolve,reject)=>{
for(let i=0;i<promises.length;i++){
promises[i].then(data=>{
processData(i,data);
},reject);
};
});
}