1.为什么要用Promise
为了解决过长的链式回调函数造成的回调地狱,使其层次结构更加清晰明朗!
//常见的回调地狱实例
const loadImg = (url,callback) => {
setTimeout(()=>{
console.log('url是:'+url);
callback();
},500)
}
loadImg('localhost/img1',()=>{
loadImg('localhost/img2',()=>{
loadImg('localhost/img3',()=>{
console.log('all done');
})
})
})
代码层次结构很乱,不便于维护
2.Promise的出现
提到promise,不得不说一个名词叫做异步任务,异步任务又分为宏任务和微任务。
其中:微任务的出现是为了在宏任务的执行中,碰到需要紧急处理的情况,而把优先级提前。
宏任务有
- setTimeout
- setInterval
- setImmediate
- ajax
微任务有
- process.nextTick
- Promise.then catch finally
执行顺序为:先宏再微:先宏任务,再清理宏任务中的微任务队列,再进行下一个宏任务队列内的宏任务,遵循先进先出原则。
举个例子(可以先看第三点promise a+规范,以便更好理解):
const p = function () {
return new Promise((resolve, reject) => {
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1)
}, 0)
resolve(2)
})
p1.then((res) => {
console.log(res);
})
console.log(3);
resolve(4);
})
}
p().then((res) => {
console.log(res);
})
console.log('end');
1.第一遍扫描:从上往下看,碰到p(),再看p方法里面,碰到p1,在看p1里面,有一个setTimeout宏任务,放入宏任务队列,碰到resolve(2),则将p1.then()方法放入微任务队列。再往下碰到console.log(3),则第一个输出为3
3
再往下看,碰到resolve(4),将p().then()方法放入微任务队列。再往下碰到console.log(‘end’),则输出end
end
2.清空微任务队列,此时微任务队列内的任务为[p1.then(),p().then()],先看p1.then(),console.log(res),res为2,则输出2
2
再看p().then(),console.log(res),res为4,则输出4
4
3.开启下一个宏任务扫描,下一个宏任务是setTimeout,往下看,碰到resolve(1),此时要注意,因为当前setTimeout的resolve已经在上一步清空为任务队列的第一个任务中已经被resolve了,所以此时的resolve(1)已经没有任何作用了,所以不输出任何东西,而不是一般想象中的输出1
4.没有任何任务了,结束。
所以最终结果输出
3
end
2
4
3.Promise A+规范
## 术语
-
promise 是一个有then方法的对象或者是函数,行为遵循本规范
-
thenable 是一个有then方法的对象或者是函数
-
value 是promise状态成功时的值,也就是resolve的参数, 包括各种数据类型, 也包括undefined/thenable或者是 promise
-
reason 是promise状态失败时的值, 也就是reject的参数, 表示拒绝的原因
-
exception 是一个使用throw抛出的异常值
## 规范
接下来分几部分来讲解PromiseA+规范.
### Promise States
promise应该有三种状态. 要注意他们之间的流转关系.
- pending
1.1 初始的状态, 可改变.
1.2 一个promise在resolve或者reject前都处于这个状态。
1.3 可以通过 resolve -> fulfilled 状态;
1.4 可以通过 reject -> rejected 状态;
- fulfilled
2.1 最终态, 不可变.
2.2 一个promise被resolve后会变成这个状态.
2.3 必须拥有一个value值
- rejected
3.1 最终态, 不可变.
3.2 一个promise被reject后会变成这个状态
3.3 必须拥有一个reason
Tips: 总结一下, 就是promise的状态流转是这样的
pending -> resolve(value) -> fulfilled
pending -> reject(reason) -> rejected
### then
promise应该提供一个then方法, 用来访问最终的结果, 无论是value还是reason.
- 参数要求
1.1 onFulfilled 必须是函数类型, 如果不是函数, 应该被忽略.
1.2 onRejected 必须是函数类型, 如果不是函数, 应该被忽略.
- onFulfilled 特性
2.1 在promise变成 fulfilled 时,应该调用 onFulfilled, 参数是value
2.2 在promise变成 fulfilled 之前, 不应该被调用.
2.3 只能被调用一次(所以在实现的时候需要一个变量来限制执行次数)
- onRejected 特性
3.1 在promise变成 rejected 时,应该调用 onRejected, 参数是reason
3.2 在promise变成 rejected 之前, 不应该被调用.
3.3 只能被调用一次(所以在实现的时候需要一个变量来限制执行次数)
- onFulfilled 和 onRejected 应该是微任务
这里用queueMicrotask来实现微任务的调用.
- then方法可以被调用多次
5.1 promise状态变成 fulfilled 后,所有的 onFulfilled 回调都需要按照then的顺序执行, 也就是按照注册顺序执行(所以在实现的时候需要一个数组来存放多个onFulfilled的回调)
5.2 promise状态变成 rejected 后,所有的 onRejected 回调都需要按照then的顺序执行, 也就是按照注册顺序执行(所以在实现的时候需要一个数组来存放多个onRejected的回调)
- 返回值
then 应该返回一个promise
```js
promise2 = promise1.then(onFulfilled, onRejected);
```
6.1 onFulfilled 或 onRejected 执行的结果为x, 调用 resolvePromise( 这里大家可能难以理解, 可以先保留疑问, 下面详细讲一下resolvePromise是什么东西 )
6.2 如果 onFulfilled 或者 onRejected 执行时抛出异常e, promise2需要被reject
6.3 如果 onFulfilled 不是一个函数, promise2 以promise1的value 触发fulfilled
6.4 如果 onRejected 不是一个函数, promise2 以promise1的reason 触发rejected
- resolvePromise
```js
resolvePromise(promise2, x, resolve, reject)
```
7.1 如果 promise2 和 x 相等,那么 reject TypeError
7.2 如果 x 是一个 promsie
如果x是pending态,那么promise必须要在pending,直到 x 变成 fulfilled or rejected.
如果 x 被 fulfilled, fulfill promise with the same value.
如果 x 被 rejected, reject promise with the same reason.
7.3 如果 x 是一个 object 或者 是一个 function
let then = 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.
4.常见的promise的使用
const get = url => {
return new Promise(r => {
$.get(url, data => {
r(data)
});
})
};
get('localhost/img1')
.then(imgUrl2=>get(imgUrl2))
.then(imgUrl3=>get(imgUrl3))
因为axios API放回的也是一个promise对象,所以也适用于promise规范
import axios from 'axios';
axios.get('localhost/img1')
.then(imgUrl2=>axios.get(imgUrl2))
.then(imgUrl3=>axios.get(imgUrl3))
5.手写一个promise A+
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class MPromise {
FULFILLED_CALLBACK_LIST = [];
REJECTED_CALLBACK_LIST = [];
_status = PENDING;
constructor(fn) {
// 初始状态为pending
this.status = PENDING;
this.value = null;
this.reason = null;
try {
fn(this.resolve.bind(this), this.reject.bind(this));
} catch (e) {
this.reject(e);
}
}
get status() {
return this._status;
}
set status(newStatus) {
this._status = newStatus;
switch (newStatus) {
case FULFILLED: {
this.FULFILLED_CALLBACK_LIST.forEach(callback => {
callback(this.value);
});
break;
}
case REJECTED: {
this.REJECTED_CALLBACK_LIST.forEach(callback => {
callback(this.reason);
});
break;
}
}
}
resolve(value) {
if (this.status === PENDING) {
this.value = value;
this.status = FULFILLED;
}
}
reject(reason) {
if (this.status === PENDING) {
this.reason = reason;
this.status = REJECTED;
}
}
then(onFulfilled, onRejected) {
const fulFilledFn = this.isFunction(onFulfilled) ? onFulfilled : (value) => {
return value;
}
const rejectedFn = this.isFunction(onRejected) ? onRejected : (reason) => {
throw reason;
};
const fulFilledFnWithCatch = (resolve, reject, newPromise) => {
queueMicrotask(() => {
try {
if (!this.isFunction(onFulfilled)) {
resolve(this.value);
} else {
const x = fulFilledFn(this.value);
this.resolvePromise(newPromise, x, resolve, reject);
}
} catch (e) {
reject(e)
}
})
};
const rejectedFnWithCatch = (resolve, reject, newPromise) => {
queueMicrotask(() => {
try {
if (!this.isFunction(onRejected)) {
reject(this.reason);
} else {
const x = rejectedFn(this.reason);
this.resolvePromise(newPromise, x, resolve, reject);
}
} catch (e) {
reject(e);
}
})
}
switch (this.status) {
case FULFILLED: {
const newPromise = new MPromise((resolve, reject) => fulFilledFnWithCatch(resolve, reject, newPromise));
return newPromise;
}
case REJECTED: {
const newPromise = new MPromise((resolve, reject) => rejectedFnWithCatch(resolve, reject, newPromise));
return newPromise;
}
case PENDING: {
const newPromise = new MPromise((resolve, reject) => {
this.FULFILLED_CALLBACK_LIST.push(() => fulFilledFnWithCatch(resolve, reject, newPromise));
this.REJECTED_CALLBACK_LIST.push(() => rejectedFnWithCatch(resolve, reject, newPromise));
});
return newPromise;
}
}
}
resolvePromise(newPromise, x, resolve, reject) {
// 如果 newPromise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 newPromise
// 这是为了防止死循环
if (newPromise === x) {
return reject(new TypeError('The promise and the return value are the same'));
}
if (x instanceof MPromise) {
// 如果 x 为 Promise ,则使 newPromise 接受 x 的状态
// 也就是继续执行x,如果执行的时候拿到一个y,还要继续解析y
x.then((y) => {
resolvePromise(newPromise, y, resolve, reject);
}, reject);
} else if (typeof x === 'object' || this.isFunction(x)) {
// 如果 x 为对象或者函数
if (x === null) {
// null也会被判断为对象
return resolve(x);
}
let then = null;
try {
// 把 x.then 赋值给 then
then = x.then;
} catch (error) {
// 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise
return reject(error);
}
// 如果 then 是函数
if (this.isFunction(then)) {
let called = false;
// 将 x 作为函数的作用域 this 调用
// 传递两个回调函数作为参数,第一个参数叫做 resolvePromise ,第二个参数叫做 rejectPromise
try {
then.call(
x,
// 如果 resolvePromise 以值 y 为参数被调用,则运行 resolvePromise
(y) => {
// 需要有一个变量called来保证只调用一次.
if (called) return;
called = true;
this.resolvePromise(promise, y, resolve, reject);
},
// 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
(r) => {
if (called) return;
called = true;
reject(r);
});
} catch (error) {
// 如果调用 then 方法抛出了异常 e:
if (called) return;
// 否则以 e 为据因拒绝 promise
reject(error);
}
} else {
// 如果 then 不是函数,以 x 为参数执行 promise
resolve(x);
}
} else {
// 如果 x 不为对象或者函数,以 x 为参数执行 promise
resolve(x);
}
}
catch (onRejected) {
return this.then(null, onRejected);
}
isFunction(param) {
return typeof param === 'function';
}
static resolve(value) {
if (value instanceof MPromise) {
return value;
}
return new MPromise((resolve) => {
resolve(value);
});
}
static reject(reason) {
return new MPromise((resolve, reject) => {
reject(reason);
});
}
static race(promiseList) {
return new MPromise((resolve, reject) => {
const length = promiseList.length;
if (length === 0) {
return resolve();
} else {
for (let i = 0; i < length; i++) {
MPromise.resolve(promiseList[i]).then(
(value) => {
return resolve(value);
},
(reason) => {
return reject(reason);
});
}
}
});
}
static all(promiseList){
return new MPromise((resolve,reject) => {
const finish = promiseFinish(promiseList,resolve);
promiseList.forEach((promise,index)=>{
promise.then(value=>{
finish(value,index);
})
})
})
}
promiseFinish(promiseList,resolve) {
const valueFinished = [];
return function(value,index) {
valueFinished[index] = value;
if(valueFinished.length === promiseList){
resolve(valueFinished);
}
}
}
}
补充:一种别的promiseA+书写方法
链接:手写promiseA+
主要处理下底下这种情况
// const promise = new MPromise((resolve,reject)=>{
// resolve(1111);
// })
// promise
// .then(res=>{console.log(res);return ‘2222’})
// .then((res)=>{console.log(res,‘xixixi’)});
// promise
// .then(console.log)
6.疑题答问
Promise.resolve().then(() => {
console.log(0);
return Promise.resolve(4);
}).then((res) => {
console.log(res)
})
Promise.resolve().then(() => {
console.log(1);
}).then(() => {
console.log(2);
}).then(() => {
console.log(3);
}).then(() => {
console.log(5);
}).then(() =>{
console.log(6);
})
1.首先思考了一下宏任务与微任务,还有promise,模拟了一下输出结果
0
1
4
2
3
5
6
实际上promise的输出结果
0
1
2
3
4
5
6
根据自写的promise和其它手写的promise的输出结果
0
1
2
4
3
5
6
为什么会有差异呢?
在console.log(0)下面一行的时候,return Promise.resolve(4),如果resolvePromise内的resolve(value)的value是一个promise对象的时候,会依次占用两个微任务队列。但是根据自写的promise的这里来看,仅仅只会占用一个微任务队列。
// 如果 x 为 Promise ,则使 newPromise 接受 x 的状态
// 也就是继续执行x,如果执行的时候拿到一个y,还要继续解析y
x.then((y) => {
resolvePromise(newPromise, y, resolve, reject);
}, reject);
但是手写的MPromise也是符合promise A+规范的一个实例,各方面请教后才知道有一句话,当执行return promise的时候需要等到当前执行栈为空的时候才会return 掉新的promise,所以才会在微任务队列中占用了两个微任务,但其实手写的promise和原生promise都是符合promise A+,所以接下来也就不细究了。