promise A+规范官网:promisesaplus.com/
一、Promise的介绍
Promise,译为承诺,是异步编程的一种解决方案,比传统的解决方案(回调函数)更加合理和更加强大
在以往我们如果处理多层异步操作,我们往往会像下面那样编写我们的代码
doSomething(function(result) {
doSomethingElse(result, function(newResult) {
doThirdThing(newResult, function(finalResult) {
console.log('得到最终结果: ' + finalResult);
}, failureCallback);
}, failureCallback);
}, failureCallback);
阅读上面代码,是不是很难受,上述形成了经典的回调地狱
现在通过Promise的改写上面的代码
doSomething().then(function(result) {
return doSomethingElse(result);
})
.then(function(newResult) {
return doThirdThing(newResult);
})
.then(function(finalResult) {
console.log('得到最终结果: ' + finalResult);
})
.catch(failureCallback);
瞬间感受到promise解决异步操作的优点:
- 链式操作减低了编码难度
- 代码可读性明显增强
状态
promise对象仅有三种状态
- pending(进行中)
- fulfilled(已成功)
- rejected(已失败)
pending
- 初始化状态,可改变
- 一个promise在resolve和reject前都处于这个状态
- 可以通过resolve --> fulfilled状态
- 可以通过reject --> rejected状态
fulfilled
- 最终态,不可变
- promise被resolve后会变成这个状态
- 必须拥有一个value值,promise成功后返回的值
rejected
- 最终态,不可变
- promise被reject后变成这个状态
- 必须拥有reason,promise失败后必须有一个错误的理由
ps:“不可变”意味着不变的身份,(即===),并不意味着深刻的不变性
promise的状态流转:
- pending --> resolve(value) --> fulfilled
- pending --> reject(reason) --> rejected
then 函数
promise必须提供一个then方法来访问当前或最终的值(value)或原因(reason)。
then接收两个参数:onFulfilled、onRejected
promise.then(onFulfilled,onRejected)
onFulfilled & onRejected
- onFulfilled、onReject两个参数如果不是函数,则会忽略。
- promise状态变成fulfilled或rejected前,onFulfilled或onReject不能调用且不能被多次调用
- 在 promise变成 fulfilled或rejected状态 的时候,应该调用 onFulfilled或onReject;
- onFulfilled的参数是value,onRejected的参数是reason
- onFulfilled和onRejected应该是微任务 可以用queueMicrotask 或者 setTimeout来实现微任务的调用
- onFulfilled和onRejected必须在执行后才能被调用,并且只能调用一次。
then可以被调用多次
- promise状态变成fulfilled后,所有的onFulfilled回调都需要按照then的顺序执行,也就是按照注册顺序执行(在实现的时候需要一个数组来存放多个onFulfilled的回调)
- promise状态变成rejected后,所有的onRejected回调都需要按照then的顺序执行,也就是按照注册顺序执行(在实现的时候需要一个数组来存放多个onFulfilled的回调)
then返回值
then应该返回一个Promise,是一个新的Promise
promise2 = promise1.then(onFulfilled,onRejected)
- onFulfilled或onRejected执行的结果为x,调用resolvePromise
- 如果onFulfilled或onRejected执行时抛出异常,promise2需要被rejected。原因就是reason。
- 如果onFulfiiled不是一个函数并且promise1为fulfilled,promise2会以promise1的value触发fulfilled
- 如果onRejected不是一个函数并且promise1为rejected,promise2会以promise1的reason触发rejected
Promise细节:
p.then()时,里面是可以接收两个函数的,一个是onFulfilled,另一个是onRejected,这时有些同志看了不免有两点疑惑,第一点疑惑是:then居然能接收两个函数?;第二个疑惑是:then如果能接收到onRejected,那为什么还需要catch?
then确实能接收到两个函数,但我们平时基本只会用到第一个onFulfilled;第二个onRejected和catch的区别是:onRejected只能处理发生在p的错误,而catch除了能处理p的错误,还能处理onFulfilled内部的错误以及p.then().then()....中每一环的错误,所以我们更习惯用catch
resolvePromise
resolvePromiseFn(promise2,x,resolve,reject)
- 如果promise2 === x,那么reject TypeError
- 如果x是一个promise:
- x是pending状态,那么promise必须保持pending状态,直到x变成fulfilled或rejected
- x被fulfilled,promise以相同的value返回
- x被rejected,promise以相同的reason的理由拒绝
- 如果x是一个function或object
let then = x.then
-
如果x.then出错,那么reject promise with e as the reason
-
如果then是一个函数,then.call(x,resolovePromise,rejectPromise),因为是函数的原因,当前的then指向不是当前的上下文,要把当前函数的this指向当前上下文。相当于then.call(x) => x.then
- 如果resolvePromise的入参是y,执行resolvePromise(promise2,y,resolve,reject)
- 如果resolvePromise的入参是r,reject promise with r
- 如果resolvePromise和resolvePromise或者多次调用同一个参数,那么第一个调用优先,后面的调用忽略
- 如果调用then抛出异常e,如果resolvePromise或rejectPromise已被调用,就忽略。否则reject promise with e as the reason
- 如果then不是一个Function,fulfill promse with x
流程
认真阅读下图,我们能够轻松了解promise整个流程
术语
promise是一个有then方法的对象或者是函数,行为遵循本规范thenable是一个有then方法的对象或者是函数value是promise状态成功时的值,也就是resolve的参数, 表示结果的数据reason是promise状态失败时的值, 也就是reject的参数, 表示拒绝的原因exception是一个使用throw抛出的异常值
二、Promise用法
1、初探 promise
先不管是什么,但是你一定用过。
let a = getSomeThing('/api/v1/user/role');
a.then(res => {
// vue
this.dataList = res.data;
// react
setData(res.data)
});
// -------- 我们来用 promise 模拟一下这个接口 ---------
function getSomeThing(url) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ data: [1,2,3] })
}, 2000);
});
};
- Promise 是一个构造函数。
- Promise 接收一个函数作为参数,这个函数的参数,是两个函数。
- Promise 返回的对象,包含一个 then 函数, 这个 then 函数,接收两个参数,这两个参数,也都是函数。
2、手写Promise
初探版
// executor执行函数
function LPromise(executor) {
this.status = 'pending';
this.value = null;
this.reason = null;
// 成功状态
const resolve = value => {
if (this.status == 'pending') {
this.status = 'fulfilled';
this.value = value;
}
};
// 失败状态
const reject = reason => {
if (this.status == 'pending') {
this.status = 'rejected';
this.reason = reason;
}
};
// 小优化:在执行executor时,也有可能代码报错,此时也应该进行reject
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
LPromise.prototype.then = function (onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : data => { return data; };
onRejected = typeof onRejected === 'function' ? onRejected : data => { return data; };
if (this.status == 'fulfilled') {
onFulfilled(this.value);
}
if (this.status == 'rejected') {
onRejected(this.reason);
}
};
new LPromise((resolve, reject) => {
setTimeout(() => {
resolve('hello')
},1000)
}).then(res => {
console.log('res===', res);
});
以上有个问题:
问题在于,我们 resolve 的时候, then 函数,已经执行过了。
所以,我们要把函数收集起来在一个合适的时机去执行它
再换句话说,我们需要在一个合适的时间,去通知 onfulfilled 执行
异步问题说明
onfulfilled 和 onrejected 应该是微任务(我们暂时用 setTimeout 代替)
function LPromise(executor) {
this.status = 'pending';
this.value = null;
this.reason = null;
this.onFulfilledArray = []; // 成功回调数组
this.onRejectedArray = []; // 失败回调数组
// 成功状态
const resolve = value => {
if (this.status == 'pending') {
this.status = 'fulfilled';
this.value = value;
this.onFulfilledArray.forEach(func => func(value));
}
};
// 失败状态
const reject = reason => {
if (this.status == 'pending') {
this.status = 'rejected';
this.reason = reason;
this.onRejectedArray.forEach(func => func(reason));
}
};
// 小优化:在执行executor时,也有可能代码报错,此时也应该进行reject
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
LPromise.prototype.then = function (onFulfilled, onRejected) {
onFulfilled =
typeof onFulfilled === 'function'
? onFulfilled
: data => {
return data;
};
onRejected =
typeof onRejected === 'function'
? onRejected
: data => {
return data;
};
if (this.status == 'fulfilled') {
onFulfilled(this.value);
}
if (this.status == 'rejected') {
onRejected(this.reason);
}
if (this.status == 'pending') {
this.onFulfilledArray.push(onFulfilled);
this.onRejectedArray.push(onRejected);
}
};
const p = new LPromise((resolve, reject) => {
setTimeout(() => {
resolve('hello');
}, 1000);
});
p.then(res => {
console.log(res, '1111');
});
p.then(res => {
console.log(res, '2222');
});
p.then(res => {
console.log(res, '3333');
});
// 11 22 33
为什么要用数组?
- then 方法, 可以多次被调用
- promise 的状态变成
fulfilled/rejected后。所有的onfulfilled/onRejected都会按照 then 的顺序执行,也就是 注册顺序执行。
const LPromise = require('./Promise2');
new LPromise((resolve, reject) => {
setTimeout(() => {
resolve('hello');
},1000)
}).then(res => {
console.log(res);
return res + 'luyi'
}).then(res => {
console.log(res);
})
then 方法,应该返回一个 promise
promise2 = promise1.then(onFulfilled, onRejected);
- onFulfilled / onRejected 的执行结果 为 x, 调用 resolvePromise;
- 如果 onFulfilled / onRejected 执行时抛出异常,我们的 promise2 需要被 reject
- 如果 onFulfilled / onRejected 不是一个函数, promise2 以 promise1 的 value / reason 触发 fulfilled 和 rejected
重点:onfulfilled 返回了一个值,是 then 返回的 promise 需要 resolve 的。
处理链式调用
function LPromise(executor) {
this.status = 'pending';
this.value = null;
this.reason = null;
this.onFulfilledArray = []; // 成功回调数组
this.onRejectedArray = []; // 失败回调数组
// 成功状态
const resolve = value => {
if (this.status == 'pending') {
this.status = 'fulfilled';
this.value = value;
this.onFulfilledArray.forEach(func => func(value));
}
};
// 失败状态
const reject = reason => {
if (this.status == 'pending') {
this.status = 'rejected';
this.reason = reason;
this.onRejectedArray.forEach(func => func(reason));
}
};
// 小优化:在执行executor时,也有可能代码报错,此时也应该进行reject
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
LPromise.prototype.then = function (onFulfilled, onRejected) {
let p2 = new Promise((resolve, reject) => {
// 新建一个Promise,并且返回
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : data => { return data; };
onRejected = typeof onRejected === 'function' ? onRejected : data => { return data; };
if (this.status == 'fulfilled') {
// onFulfilled 成功的 .then 回调
let x = onFulfilled(this.value);
resolve(x);
}
if (this.status == 'rejected') {
onRejected(this.reason);
}
if (this.status == 'pending') {
this.onFulfilledArray.push(onFulfilled);
this.onRejectedArray.push(onRejected);
}
});
return p2;
};
const p = new LPromise((resolve, reject) => {
resolve('hello');
});
p.then(res => {
console.log(res, '1111');
return 2;
}).then(res => {
console.log(res, '2222');
return 3;
}).then(res => {
console.log(res, '3333');
});
处理循环调用问题
在真实的Promise中,我们按以下方式调用:
const p = new Promise((resolve, reject) => {
resolve(1);
})
const p2 = p.then(res => {
console.log(res);
return p2;
})
运行时会发现抛Uncaught (in promise) TypeError: Chaining cycle detected for promise #异常,这是因为p2需要接收p.then的结果,而p2又作为p.then的结果返回,如此便陷入了死循环
但是Promise给我们做了这块异常处理,所以我们也需要在我们的代码中处理这块的异常
function LPromise(executor) {
this.status = 'pending';
this.value = null;
this.reason = null;
this.onFulfilledArray = []; // 成功回调数组
this.onRejectedArray = []; // 失败回调数组
// 成功状态
const resolve = value => {
if (this.status == 'pending') {
this.status = 'fulfilled';
this.value = value;
this.onFulfilledArray.forEach(func => func(value));
}
};
// 失败状态
const reject = reason => {
if (this.status == 'pending') {
this.status = 'rejected';
this.reason = reason;
this.onRejectedArray.forEach(func => func(reason));
}
};
// 小优化:在执行executor时,也有可能代码报错,此时也应该进行reject
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
LPromise.prototype.then = function (onFulfilled, onRejected) {
let p2 = new Promise((resolve, reject) => {
// 新建一个Promise,并且返回
onFulfilled =
typeof onFulfilled === 'function'
? onFulfilled
: data => {
return data;
};
onRejected =
typeof onRejected === 'function'
? onRejected
: data => {
return data;
};
if (this.status == 'fulfilled') {
// onFulfilled 成功的 .then 回调
// 设置定时器的原因是在上面过程中,p2还没创建完毕,故需要开启定时器,等p2创建完成才能传值过去
setTimeout(() => {
let x = onFulfilled(this.value);
resolvePromise(x, resolve, reject, p2); //将p2传进去
}, 0);
}
if (this.status == 'rejected') {
onRejected(this.reason);
}
if (this.status == 'pending') {
this.onFulfilledArray.push(onFulfilled);
this.onRejectedArray.push(onRejected);
}
});
return p2;
};
function resolvePromise(x, resolve, reject, p2) {
// 将x和p2对比,如果相同则抛出异常
if (x === p2) {
return reject(
new TypeError(
'Uncaught (in promise) TypeError: Chaining cycle detected for promise #<Promise>'
)
);
}
if (x instanceof Promise) {
x.then(
value => {
resolve(value);
},
err => {
reject(err);
}
);
} else {
resolve(x);
}
}
const p1 = new LPromise((resolve, reject) => {
resolve('1');
});
const p2 = p1.then(res => {
console.log('res===', res);
return p2;
});
完整代码
function LPromise(executor) {
this.status = 'pending';
this.value = null;
this.reason = null;
this.onFulfilledArray = []; // 成功回调数组
this.onRejectedArray = []; // 失败回调数组
// 成功状态
const resolve = value => {
if (this.status == 'pending') {
this.status = 'fulfilled';
this.value = value;
this.onFulfilledArray.forEach(func => func(value));
}
};
// 失败状态
const reject = reason => {
if (this.status == 'pending') {
this.status = 'rejected';
this.reason = reason;
this.onRejectedArray.forEach(func => func(reason));
}
};
// 小优化:在执行executor时,也有可能代码报错,此时也应该进行reject
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
LPromise.prototype.then = function (onFulfilled, onRejected) {
// 新建一个Promise,并且返回
onFulfilled =
typeof onFulfilled === 'function'
? onFulfilled
: data => {
return data;
};
onRejected =
typeof onRejected === 'function'
? onRejected
: data => {
return data;
};
let p2 = new Promise((resolve, reject) => {
if (this.status == 'fulfilled') {
// onFulfilled 成功的 .then 回调
// 设置定时器的原因是在上面过程中,p2还没创建完毕,故需要开启定时器,等p2创建完成才能传值过去
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(p2, x, resolve, reject);
} catch (err) {
reject(err);
}
}, 0);
}
if (this.status == 'rejected') {
try {
let x = onRejected(this.reason);
resolvePromise(p2, x, resolve, reject);
} catch (err) {
reject(err);
}
}
if (this.status == 'pending') {
this.onFulfilledArray.push(() => {
// 异步
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(p2, x, resolve, reject);
} catch (err) {
reject(err);
}
}, 0);
});
this.onRejectedArray.push(() => {
// 异步
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(p2, x, resolve, reject);
} catch (err) {
reject(err);
}
}, 0);
});
}
});
return p2;
};
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, y, resolve, reject);
},
err => {
// 成功和失败只能调用一个
if (called) return;
called = true;
reject(err); // 失败就失败了
}
);
} else {
resolve(x); //直接成功即可
}
} catch (err) {
// 也属于失败
if (called) return;
called = true;
// 取出then出错了就不要继续执行
reject(err);
}
} else {
resolve(x);
}
}
const p = new LPromise((resolve, reject) => {
resolve('hello');
});
p.then(res => {
console.log(res, '1111');
return new Promise((resolve, reject) => {
resolve(22222);
});
})
.then(res => {
console.log(res, '2222');
return 3;
})
.then(res => {
console.log(res, '3333');
});
如何实现 resolvePromise(非重点)
resolvePromise(promise2, x, resolve, reject);
resolvePromise 的规范
- 如果 promise2 和 x 相等,那么 reject error;
- 如果 x 是一个 promise
-
- 如果 x 是一个 pending 状态,那么 promise 必须要在 pending, 直到 x 变成 fulfilled or rejected
- 如果 x 被 fulfilled, fulfill promise with the same value
- 如果 x 被 rejected, reject promise with the same reason
- 如果 x 是一个 object 或者 function
-
-
let thenable = x.then
-
如果 x.then 这一步出错,那么 reject promise with e as the reason
-
如果 then 是一个函数,then.call(x, resolvePromiseFn, rejectPromise)
-
resolvePromiseFn 的 入参是 y, 执行 resolvePromise(promise2, y, resolve, reject);
rejectPromise 的 入参是 r, reject promise with r.
如果 resolvePromise 和 rejectPromise 都调用了,那么第一个调用优先,后面的调用忽略。
如果调用then抛出异常e
如果 resolvePromise 或 rejectPromise 已经被调用,那么忽略
则,reject promise with e as the reason
如果 then 不是一个function. fulfill promise with x.
三、面试常考
1、如何并发执行Promise
const promiseArrGenerator = (num) => new Array(num).fill(0).map((item, index) => () => new Promise((resolve, reject) => {
setTimeout(() => resolve(index), Math.random() * 10)
}));
const proArr = promiseArrGenerator(100);
Promise.all(proArr.map(fn => fn())).then(res => console.log(res))
2、promiseChain 顺序执行这些 promise
// proArr.forEach(fn => fn().then(res => console.log(res)));
// 执行顺序是乱的
// 按顺序执行
const promiseArrGenerator = num =>
new Array(num).fill(0).map(
(item, index) => () =>
new Promise((resolve, reject) => {
setTimeout(() => resolve(index), Math.random() * 10);
})
);
const proArr = promiseArrGenerator(10);
const promiseChain = proArr => {
proArr
.reduce(
(prochain, pro) =>
prochain.then(res => {
// 初始化-1不打印
~res && console.log(res, 111);
return pro();
}),
Promise.resolve(-1)
)
.then(res => console.log(`the last one is ${res}`));
};
promiseChain(proArr);
3、设计一个 pipe, 我去并发执行一部分。
// 例子:chrome 6 个并发。100 接口。
// 3. 设计一个 pipe, 我去并发执行一部分。
// chrome 6 个并发。100 接口。
const promiseArrGenerator = num =>
new Array(num).fill(0).map(
(item, index) => () =>
new Promise((resolve, reject) => {
setTimeout(() => resolve(index), Math.random() * 5000);
})
);
const proArr = promiseArrGenerator(100);
const promisePipe = (proArr, concurrent) => {
if (concurrent > proArr.length) {
return Promise.all(proArr.map(fn => fn())).then(res => console.log(res));
}
let _arr = [...proArr];
// 100 个 函数, 并发 ------ 保持当前请求一直有6个
for (let i = 0; i < concurrent; i++) {
let fn = _arr.shift();
run(fn);
}
function run(fn) {
fn().then(res => {
console.log(res);
if (_arr.length) {
run(_arr.shift());
}
});
}
};
promisePipe(proArr, 6);