前言
只记录常用的方法,更加详细去阮一峰老师的网站查看Promise
有关Promise想要刷题的建议这里。超级厉害!!!!
一、基础知识
1.Promise对象特点
熟记这些特点
自身特点
-
对象状态不受外界影响 对象的状态总共有三种(pending、fulfilled、rejected),只有异步操作结果能决定是哪一种状态,其他手段无法改变
-
状态改变,就定型 状态改变只有两种情况(pending——>fulfilled,pending——>rejected),一旦状态改变,状态就凝固,并且一直保持这个结果。
-
无法取消 一旦新建就会立即执行,状态为pending,中途无法取消
与Event Loop的联系
- new Promise立即执行
Promise构造函数中的代码是同步执行的。并且
resolve/reject是不会终结Promise的参数函数执行的
new Promise((resolve, reject) => {
resolve(1);
console.log(2);
}).then(r => {
console.log(r);
});
//2
//1
- 回调函数是微任务
当
Promise状态不为pending并且调用Promise.then(),这时会把对应的回调函数添加到微任务队列中
只有Promise的状态不为pending时,才会把对应then、catch、finnaly回调函数加入微任务队列中,所以需要注意P调用这些方法的promise状态变化时机,容易和微任务和宏任务队列相关结合
2.基本用法
new Promise
const promise = new Promise(function(resolve, reject) {
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
-
Promise是一个构造函数,接收一个函数为参数,函数参数为resolve和reject
-
resolve和reject的作用就是将pending状态改变为resolved/rejected
-
Promise生成的实例保存着异步操作的结果(Promsise对象的状态),比如像以下这样
Promise { <pending> } 、Promise { 1 }、Promise { <rejected> 1 }
Promise.prototype.then()
then方法是定义在构造函数原型上的方法,作用: 为Promise实例添加状态改变时的回调函数。
promise.then(function(value) {
// success
}, function(error) {
// failure
});
- then方法接收俩个回调函数作为参数
- promise实例保存的结果(Promise对象的状态)决定触发哪一个回调函数
- 如果promise对象状态为pending的话,就不会触发上述的回调函数
then方法返回的是一个新的Promise实例--详细用法在进阶用法中
Promise.prototype.catch()
catch()方法是.then(null,rejection)或then(undefined,rejection)的别名,用来指定发生错误时的回调函数.
const promise = new Promise(function(resolve, reject) {
throw new Error('test');
});
promise.then(res=>{
},rej=>{
})
.catch( function(error) {
// failure
});
- 参数是一个回调函数
- 当Promise对象状态变为rejected,就会调用catch()方法指定的回调函数
- 当Promise对象抛出错误时,也会被catch捕获
- then()方法指定的回调函数,如果运行中抛出错误,也是由catch()方法捕获
catch()方法返回的是一个Promise实例
小结:
关于catch和then方法,后续会讲链式调用的问题。还得记住他们的一个特点-- 返回的值不能是 promise 本身,否则会造成死循环。
const promise = Promise.resolve().then(() => {
return promise;
})
promise.catch(console.err)
Promise.prototype.finally()
promise结束时,无论结果是fulfilled或者是rejected,都会执行指定的回调函数
-
.finally()方法的回调函数不接受任何的参数,也就是说你在.finally()函数中是没法知道Promise最终的状态是resolved还是rejected的 -
finally回调函数内部最终返回的默认会是一个上一次的Promise对象值,不过如果抛出的是一个异常则返回异常的
Promise对象。
Promise.resolve('2')
.finally(() => {
console.log('finally2')
return '我是finally2返回的值'
})
.then(res => {
console.log('finally2后面的then函数', res)
})
//打印 finnaly2-->finally2后面的then函数,2
抛出错误
Promise.resolve('1')
.finally(() => {
console.log('finally1')
throw new Error('我是finally中抛出的异常')
})
.then(res => {
console.log('finally后面的then函数', res)
})
.catch(err => {
console.log('捕获错误', err)
})
'finally1'
'捕获错误' Error: 我是finally中抛出的异常
Promise.resolve()
作用: 转换成为一个新的Promise对象。
Promise.resolve(value)等价于new Promise(resolve => resolve(value))
参数主要有四种情况:
-
参数是一个Promise实例 不做任何改变,原封不动的返回这个实例
-
参数是thenable对象 也就是说具有then方法的对象,
let thenable = {
then: function(resolve, reject) {
resolve(42);
}
};
let p1 = Promise.resolve(thenable);
p1.then(function (value) {
console.log(value); // 42
});
- 参数是和Promise没关系的值
可以是原始值,也可以是普通对象,这时候会返回一个状态为
resolved的Promise对象
const p = Promise.resolve('Hello');
p.then(function (s) {
console.log(s)
});
- 不带任何参数 返回一个resolved状态的 Promise 对象。
Promise.reject()
作用: 转换成为一个新的Promise对象。该实例的状态为rejected。
Promise.reject()方法的参数,会原封不动地作为reject的理由,变成后续方法的参数。
Promise.reject('出错了')
.catch(e => {
console.log(e)//出错了
})
Promise.all()
这个方法用于多个Promise实例,返回是是一个新的Promise实例。
Promise.all就是多个异步任务并发运行,它的结果在创建承诺之后使用,等待所有任务结果完成。
- 参数:
- 接收一个数组作为参数,参数需要都是promise实例,如果不是会调用Promise.resolve()将传入的参数转化为Promise实例。
Promise.all([
1,
'1'
//相当于Promise.resolve(1)
])
.then((res)=>{
console.log(res);// [1,'1']
})
- 参数也可以不是一个数组 但是得有Iterator接口,且每个成员都是Promise实例
- 返回值
const p = Promise.all([p1, p2, p3]); p的状态是由p1、p2、p3决定的
- 返回有状态的Promise
只有当数组中promise实例状态都为resolved时,或者其中有一个变为rejected,调用then/catch方法才会去触发回调函数
var p1 = new Promise((res,rej)=>{
//无状态
})
var p2 = new Promise((res,rej)=>{
res("p2 res")
})
var all = Promise.all([
p1,p2
])
console.log(all)//Promise { <pending> }
all.then((res)=>{
console.log("success---"+res);
})
.catch((err)=>{
console.log("fail---"+err);
})
- 返回resolved状态的数组
当数组中返回的都是resolved状态的Promise,则返回一个触发所有回调函数的结果值的数组
Promise.all([
1,
'1'
//相当于Promise.resolve(1)
])
.then((res)=>{
console.log(res);// [1,'1']
})
- 返回rejected状态的Promise
当数组中返回的Promise实例只要有一个为rejeced,整体的返回结果就是触发的第一个失败状态的回调函数
var p1 = new Promise((res,rej)=>{
res("p1 res")
})
var p2 = new Promise((res,rej)=>{
rej("p2 rej")
})
var p3 = 1;
var p4 = new Promise((res,rej)=>{
rej("p4 rej")
})
Promise.all([
p1,p2,p3,p4
])
.then((res)=>{
console.log("success---"+res);
})
.catch((err)=>{
console.log("fail---"+err);//fail---p2 rej
})
注意: 即使Promise.all参数数组中第一个Promise实例已经是rejected,整个Promise.all的状态将为rejected。但是其参数数组rejected状态后面的promise还是会执行的
- rejected阻塞所有 当Promise.all中一个rejected导致返回的Promise状态为rejected,就不能接收到别的参数中promise实例的状态
可以使用catch去接收不论什么状态的promise实例,从而最终都能到Promise.all的then回调之中
var p1 = new Promise((resolve, reject) => {
resolve('p1');
});
var p2 = new Promise((resolve, reject) => {
resolve('p2');
});
var p3 = new Promise((resolve, reject) => {
reject('p3');
});
Promise.all([p1, p2, p3].map(p => p.catch(e => '出错后返回的值' )))
.then(values => {
console.log(values);
}).catch(err => {
console.log(err);
})
3.进阶用法
当Promise实例作为resolve参数
const p1 = new Promise(function (resolve, reject) {
reject("p1 rejected")
});
const p2 = new Promise(function (resolve, reject) {
resolve(p1);
})
p2.then((res)=>{
console.log("p2 resolved")
},(rej)=>{
console.log(rej);//p1 rejected
})
上述代码,打印为p1 rejected
- 很显然,
一个异步操作的结果p2是返回另一个异步操作p1 - p1的状态会传递给p2,因此p2的状态是由p1的返回结果决定的,而不是由p2的Promise对象resolve决定的。
链式调用(重要)
提起链式调用,不知道你们想起Jquery没有,它就是有链式调用的,但是它的链式调用依靠的是在Jquery函数内部 return一个this,从而实现的。而then方法是通过return一个新的promise
由上面的基本用法,我们可以知道catch()、then()方法的返回结果是一个新的Promise实例,因此可以接着调用catch(),then()方法。
关于链式调用我们明确以下几点:
- new Promise对象状态为pending时,是不会触发then方法中的回调函数的
const p1 = new Promise(function (resolve, reject) {
});
p1.then((res)=>{
console.log(" resolved")
},(rej)=>{
console.log("rejected");
})
- then方法会根据上一个then方法或者catch方法返回的Promise对象状态,决定then方法触发哪个回调函数。具体区分为以下两种情况:
- 当前then方法触发resolved回调函数
第一,上一个then
无返回值或者return一个值,这时候默认返回Promise.resolve(undefined)或者Promise.resolve(value)
注意:这里的return值,是不管上一个then触发的是哪一个回调函数,即使上一个then触发的是失败的回调函数,只要它return undefined/普通值,此时的then都是触发成功的回调函数
注意:return new Error()也是返回一个resolved状态的promise,和后面的throw new Error()是不一样的!!!!
const p1 = new Promise(function (resolve, reject) {
resolve("p1 resolve")
});
p1.then((res)=>{
console.log(res)
// Promise.resolve(undefined)
},(rej)=>{
console.log("rejected");
})
.then((res)=>{
console.log(res)//打印undefined
},rej=>{
});
以上代码,在第一次then方法中会返回一个resolved状态的Promise对象,并且值为undefined
第二,上一个then
return一个新的promise成功态结果(resolve)
const p1 = new Promise(function (resolve, reject) {
resolve("p1 resolve")
});
p1.then((res)=>{
console.log(res)
return new Promise((res,rej)=>{
res("first then resolve");
})
},(rej)=>{
console.log("rejected");
})
.then((res)=>{
console.log(res)//打印"first then resolve"
},rej=>{
});
- 当前then方法触发rejected回调函数
第一,上一个then
return了一个新的promise失败态的结果(reject)
const p1 = new Promise(function (resolve, reject) {
resolve("p1 resolve")
});
p1.then((res)=>{
console.log(res)
return new Promise((res,rej)=>{
rej("first then reject");
})
},(rej)=>{
console.log("rejected");
})
.then((res)=>{
console.log(res)
},rej=>{
console.log(rej)//first then reject
});
第二,上一个then
throw new error 抛出了异常
const p1 = new Promise(function (resolve, reject) {
resolve("p1 resolve")
});
p1.then((res)=>{
console.log(res)
throw new Error("抛出 reject")
},(rej)=>{
console.log("rejected");
})
.then((res)=>{
console.log(res)
},rej=>{
console.log(rej)//Error: 抛出 reject
});
- 联系Promise.all 看看这个题 和Promise.all结合起来做做看
const p1 = new Promise((resolve, reject) => {
resolve('hello');
})
.then(result => result)
.catch(e => e);
const p2 = new Promise((resolve, reject) => {
throw new Error('报错了');
})
.then(result => result)
.catch(e => e);
Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// ["hello", Error: 报错了]
p1会resolved,p2首先会rejected,但是p2有自己的catch方法,该方法返回的是一个新的 Promise 实例,p2指向的实际上是这个实例。该实例执行完catch方法后,也会变成resolved,导致Promise.all()方法参数里面的两个实例都会resolved,因此会调用then方法指定的回调函数,而不会调用catch方法指定的回调函数。
Promise对象的错误有‘冒泡’性质
这个性质其实也是基于链式调用才能实现的。
一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获或者then方法rejected回调函数捕获。
const p1 = new Promise(function (resolve, reject) {
resolve("p1 resolve")
});
p1.then((res)=>{
console.log(res)//p1 resolve
throw new Error("抛出 reject")
},(rej)=>{
console.log("rejected");
})
.then((res)=>{},rej=>{
console.log(rej+"---then捕获")//Error: 抛出 reject---then捕获
})
.catch(err=>{
console.log(err+"---catch捕获")
})
then、catch参数问题
.then 或者 .catch 的参数期望是函数,传入非函数则会发生值透传。
//输入结果为5
Promise.resolve(1)
.then(2)
.then(Promise.resolve(3))
.then(console.log)
//输出结果为5
Promise.resolve(1)
.then(res=>{
return 5
})
.then(Promise.resolve(3))
.then(console.log)
在Event Loop中的运用
- promise.then是一个微任务,所以要注意它的执行时间
在promise.then还没执行之前,它返回的一直是一个pending状态的Promise
const promise1 = new Promise((resolve, reject) => {
console.log('promise1')
resolve('resolve1')
})
const promise2 = promise1.then(res => {
console.log(res)
});
console.log('2', promise2);
上述打印顺序为
具体原因为:
-
先遇到new Promise,执行该构造函数中的代码promise1 碰到resolve函数, 将promise1的状态改变为resolved, 并将结果保存下来
-
碰到promise1.then这个微任务,并且状态不为pending,将对应的回调函数放入微任务队列 -
promise2是一个新的状态为pending的Promise
-
执行同步代码‘2’,同时打印出promise2的状态是pending
-
宏任务执行完毕,查找微任务队列,发现promise1.then这个微任务并且执行它,状态更改为resolved
结合setTimeout使用
- setTimeout 它是一个宏任务,但是不会被放在现有宏任务队列,而是下一个宏任务队列
const promise = new Promise((resolve, reject) => {
console.log(1);
setTimeout(() => {
console.log("timerStart");
resolve("success");
console.log("timerEnd");
}, 0);
console.log(2);
});
promise.then((res) => {
console.log(res);
});
console.log(4);
以上代码输出
可以自己结合着之前总结的那些,分析分析。有关宏任务和微任务只有具体问题具体分析。
二、实际应用
1. 实现Ajax操作
Promise实际上是充当ajax获取后台数据后执行回调函数的异步执行顺序的一个媒介。
const getJson = function(url){
const promise = new Promise((resolve,reject){
const handler = function () {
if(this.readyState!==4){
return;
}
if(this.status ===200){
resolve(this.responseText);
}else{
reject(new Error(this.statusText));
}
}
const xhr = new XMLHttpRequest();
xhr.open("GET",url);
xhr.onreadystatechange = handler;
xhr.setRequestHeader("Accept","application/json");
xhr.send();
})
return promise;
}
getJson("http://localhost:8000").then((json)=>{
console.log(json);
},(error)=>{
console.log(error);
})
2.异步加载图片
function getImg(url) {
return new Promise((resolve,reject){
let img = new Image();
img.onload = function () {
console.log("img 加载完成");
resolve(img)
}
img.onerror = function(){
reject("fail")
}
img.src = url;
})
}
三、重写Promise(重要)
主要是then、catch方法
1.实现最基本Promise构造函数
首先清楚我们要实现的点子:
- new Promise时其参数(executor)自动执行
- promise状态首先为pending,之后通过executor函数中参数resolve/reject改变状态
- then方法根据Promise状态触发对应的回调函数
- Promise能够throw new Error 并且then方法第二个参数能够捕获
抛出错误是在executor执行之后,因此利用try catch来捕获错误
const PENDING = 'PENDING',
FULFILLED = 'FULFILLeD',
REJECTED = 'REJECTED';
class MyPromise {
constructor(executor) {
// 声明状态
this.status = PENDING
//对应resolve和reject的值
this.value = undefined
this.reason = undefined
// resolve参数
const resolve = (value) => {
if (this.status === PENDING) {
this.status = FULFILLED
this.value = value
}
}
// reject参数
const reject = (reason) => {
//状态改变
if (this.status === PENDING) {
this.status = REJECTED
this.reason = reason
}
}
//捕获错误
try{
//执行构造函数
executor(resolve);
}catch(e){
reject(e);
}
}
then(onFulfilled,onRejected){
if(this.status===FULFILLED){
onFulfilled(this.value);
}
if(this.status===REJECTED){
onFulfilled(this.reason);
}
}
}
module.exports = MyPromise;
以上我们就实现了基本的Promise,接下来是查找问题了。
2.处理pending状态(异步处理)
Promise本身就是用来处理异步程序的对吧,那我添加上异步程序,发现没有变化
主要原因就是实现MyPromise中因为setTimeout函数异步的关系,函数中的状态还是pending,所以之后的promise1.then不能处理。
因此我们在promise.then时收集回调函数,在executor参数状态改变时去执行,这只是针对pending状态的promise。不会影响到其他状态。
需要实现的功能: 处理pending状态 使用了发布-订阅的模式
下面代码只放修改了的部分:
constructor(executor) {
//收集成功、失败的回调函数
this.onResolveCallbacks = [];
this.onRejectedCallbacks = [];
// resolve参数
const resolve = (value) => {
if (this.status === PENDING) {
//发布 异步任务到时间执行
this.onResolveCallbacks.forEach((fn)=>fn());
}
}
// reject参数
const reject = (reason) => {
if (this.status === PENDING) {
//发布 异步任务到时间执行
this.onRejectedCallbacks.forEach((fn)=>fn());
}
}
}
//then方法
then(onFulfilled,onRejected){
//处理pending状态 订阅的过程
if(this.status===PENDING){
//收集成功的回调
this.onResolveCallbacks.push(()=>{
onFulfilled(this.value)
})
//收集失败的回调
this.onRejectedCallbacks.push(()=>{
onRejected(this.reason);
})
}
}
}
3.实现链式调用(重要)
很明显之前实现的myPromise是不能进行链式掉用的,所以接下来是实现链式调用。
首先得清楚Promise链式调用基本特点。可以查看之前归纳的特点。
实现功能:
- then方法会返回一个promise对象
then(onFulfilled,onRejected){
let promise2 = new Promise((resolve,reject)=>{
//之前的coding
})
return promise2
}
- then方法两个回调函数是异步的 因为之后我们需要对x进行判断,需要取到Promise2这个对象,前提是这个回调函数得执行,因此是需要对回调函数进行异步处理的
利用setTimeOut对回调函数进行异步处理
- then方法触发回调函数执行后会返回一个结果由x接收。 对于这个返回结果x需要进行判断处理。resolvePromise函数处理这个返回结果
对于resolvePromise函数参数主要有四个
接下来是这个函数的逻辑:
- 回调函数return的值不能是返回的promise对象
- 判断x是不是一个promise对象,不是一个promise对象时直接resolve()就好。
- 获取x.then时可能会throw new Error 在获取x.then这个值时,可能会被拦截,从而抛出异常,因此也需要去处理
- 当判断x是一个promsie对象时,将then方法的this指向改变,并传递回调函数
- 如果x是一个Promise对象,只有第一个resolve/reject才会生效
- 当新返回的x再次resolve新的promise对象,会形成嵌套
- 当promise.then()不传入参数,会导致后面的onFulfilled...无法使用,因此需要传递默认值
- 返回结果x是throw new Error需要去捕获异常 像下面的代码一样,在回调函数执行结束之后,去捕获异常
4.实现catch方法
因为catch方法基本和then方法一致,只是第一个参数不同,只需要返回当前实例对象then方法,第一个参数赋值为空就好
5.总结代码
可以去试一下,应该是没问题的
const PENDING = 'PENDING',
FULFILLED = 'FULFILLED',
REJECTED = 'REJECTED';
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise #<MyPromise>'))
}
let called = false;
if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
try {
let then = x.then; // throw error
if (typeof then === 'function') { // Promise
then.call(x, (y) => {
if (called) return;
called = true;
resolvePromise(promise2, y, resolve, reject);
}, (r) => {
if (called) return;
called = true;
reject(r);
})
} else {
resolve(x);
}
} catch (e) {
if (called) return;
called = true;
reject(e);
}
} else {
resolve(x);
}
}
class MyPromise {
constructor(executor) {
this.status = PENDING;
this.value = undefined;
this.reason = undefined;
this.onResolveCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = (value) => {
if (value instanceof MyPromise) {
value.then(resolve, reject);
return;
}
if (this.status === PENDING) {
this.status = FULFILLED;
this.value = value;
// 发布
this.onResolveCallbacks.forEach((fn) => fn());
}
}
const reject = (reason) => {
if (this.status === PENDING) {
this.status = REJECTED;
this.reason = reason;
// 发布
this.onRejectedCallbacks.forEach((fn) => fn());
}
}
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
// x 普通值 promise
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };
let promise2 = new MyPromise((resolve, reject) => {
if (this.status === FULFILLED) {
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
}
if (this.status === REJECTED) {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
}
if (this.status === PENDING) {
// 订阅
this.onResolveCallbacks.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 (errorCallback) {
return this.then(null, errorCallback);
}
}
module.exports = MyPromise;
四、Promise.all重写
Promise.all = function(promiseArr) {
let index = 0, result = []
return new Promise((resolve, reject) => {
promiseArr.forEach((p, i) => {
Promise.resolve(p).then(val => {
index++
result[i] = val
if (index === promiseArr.length) {
resolve(result)
}
}, err => {
reject(err)
})
})
})
}