Promise详细入门
Promise算是面试经常会问的问题,而且之前没怎么搞懂,打算花一天的时间弄懂Promise,包括他所有的方法。
一天是弄不懂了,两天也弄不懂,这个Promise很玄学,还好这篇文章是入门级别的,后续更难得我也搞不定了。
Promise是个对象,被创建出来并不知道状态。存在三种状态:
- 待定(pending): 初始状态,既没有被兑现,也没有被拒绝。
- 已兑现(fulfilled): 意味着操作成功完成。
- 已拒绝(rejected): 意味着操作失败。
Promise.prototype.then 和 Promise.prototype.catch 方法返回的是 promise,所以可以套娃链式调用。
链式调用
可以用这些方法把promise.then(),promise.catch() 和 promise.finally() 串联起来。
const myPromise =
(new Promise(myExecutorFunc))
.then(handleFulfilledA,handleRejectedA)
.then(handleFulfilledB,handleRejectedB)
.then(handleFulfilledC,handleRejectedC);
// 或者,这样可能会更好...
const myPromise =
(new Promise(myExecutorFunc))
.then(handleFulfilledA)
.then(handleFulfilledB)
.then(handleFulfilledC)
.catch(handleRejectedAny);
任何不是 throw 的终止都会创建一个”已决议(resolved)”状态,而以 throw 终止则会创建一个”已拒绝”状态。
.catch只是没有预留参数的.then()而已
构造函数
语法
new Promise(executor)
executor =(resolve,reject)=>{
}
这是一个双参函数,参数为resolve和reject。Promise的实现会立即执行executor,并传入resolve和reject函数(Promise构造器将会在返回新对象之前executor)。当resolve和reject函数被调用时,它们分别对promise执行resolve和reject。executor通常会触发一些异步运算,一旦运算成功完成,则resolve掉这个promise,如果出错则reject掉。如果executor函数执行时抛出异常,promise状态会变为rejected。executor的返回值也会被忽。reject返回的通常是一个error对象。
静态方法
Promise.all()
Promise.all(iterable) 方法接收一个promise的iterable类型的输入,并且只返回一个Promise实例,那个输入的所有promise的resolve回调的结果是一个数组。所有输入的promise的resolve的回调都结束了,才会返回这个方法的resolve。如果输入数组中有一个reject执行或者输入不合法的promise就会立马抛出错误,并且reject是第一个抛出的错误信息。
传入的参数为Array或者String类型。
- 如果传入的参数是空的可迭代对象,直接同步返回已完成状态的Promise
- 如果传入的参数不包含任何promise,返回一个异步完成的Promise
- 其它情况返回pending的Promise,返回值上面说了。返回值将会按照参数内的
promise顺序排列,而不是由调用promise的完成顺序决定。
同步和异步的展示,一般情况都是异步完成,当且仅当传入空的可迭代对象
var p = Promise.all([]); // 会被立马返回,同步的状态
var p2 = Promise.all([1337, "hi"]); // 非Promise的值会被略过,但是评估还是会异步完成
console.log(p);
console.log(p2)
setTimeout(function(){
console.log('the stack is now empty'); // 使用setTimeout,在栈为空的时候才会被执行
console.log(p2);
});
// 输出,空迭代对象立马返回
// Promise { <state>: "fulfilled", <value>: Array[0] }
// Promise { <state>: "pending" }
// the stack is now empty
// Promise { <state>: "fulfilled", <value>: Array[2] }
有一个有趣的现象,自己测试在这个例子里面,由于有reject,所以遇到reject会立马返回555,但是此时其他函数还在被执行,并没有被打断,经过了10秒之后,程序才正常关闭。也就是说all的确遇到reject会立马返回,但不会中断其他promise程序,让他们空跑,盲猜是这个是其他线程在跑(浏览器的线程或者nodejs的v8?),但是js管不到,只能让他们慢慢跑。
var p1 = Promise.resolve(3);
var p2 = Promise.reject(555);
var p3 = new Promise((resolve, reject) => {
setTimeout(resolve, 10000, 'foo');
});
Promise.all([p1, p2, p3])
.then((value) => console.log(value))
.catch((err) => console.log(err));
Promise.allSettled()
该Promise.allSettled()方法返回一个在所有给定的promise都已经fulfilled或rejected后的promise,并带有一个对象数组,每个对象表示对应的promise结果。
Promise.allSettled()适合多个彼此不依赖的异步任务,想知道每个执行结果。
Promise.all()适合彼此相互依赖或者在任何一个reject的时候结束。
返回的对象有两个属性,不管是成功还是失败,都有status属性,如果值为fulfilled,那么另一个属性就是value‘’;如果值为rejected,那么另一个属性是reason
Promise.any()
这个方法挺新的,在chrome85或者nodejs15才正式支持。
和all()相反,只要有一个promise成功,就会返回那个成功的promise。如果没有一个成功,全部失败了,那么就会返回一个失败的promise和AggregateError类型的实例,用于把单一的错误集合在一起。
- 传入一个空的可迭代对象,返回一个已失败
- 不包含任何promise,返回一个异步完成的promise
- 只要有一个成功或者全部失败,才会变状态。
Promise.race()
返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。
这个也挺好理解的,如果有多个settled的,那么就按迭代顺序返回第一个值。
Promise.resolve()
Promise.resolve(value);
value:将被Promise对象解析的参数,也可以是一个Promise对象,或者是一个thenable。
Promise.resolve(value)方法返回一个以给定值解析后的Promise对象。如果这个值是一个 promise ,那么将返回这个 promise;如果这个值是thenable(即带有then方法),返回的promise会“跟随”这个thenable的对象,采用它的最终状态。
Promise.reject()
Promise.reject()方法返回一个带有拒绝原因的Promise对象。
实例方法
Promise.prototype.then()
then返回还是一个Promise对象,参数最多放两个,成功(可选)和失败(可选)的回调函数
p.then(onFulfilled[, onRejected]);
p.then(value => {
// fulfillment
}, reason => {
// rejection
});
onFulfilled函数有一个参数,即接受的最终结果。如果onFulfilled不是函数,那么会转换为(x)=>x,也就是直接返回promise的结果。
onRejected函数有一个参数,即拒绝的原因。如果onRejected不是函数,则会被替换为 Thrower 函数。
如果函数抛出错误或返回一个拒绝的Promise,则 then 将返回一个拒绝的Promise。(估计是当且仅当?)
Promise.resolve()
.then(() => {
// 使 .then() 返回一个 rejected promise
throw new Error('Oh no!');
})
.then(() => {
console.log('Not called.');
}, error => {
console.error('onRejected function called: ' + error.message);
});
在其他情况下,一个 resolving Promise 会被返回。在下面的例子里,第一个 then() 会返回一个用 resolving Promise 包装的 42,即使之前的 Promise 是 rejected 的。
Promise.reject()
.then(() => 99, () => 42) // onRejected returns 42 which is wrapped in a resolving Promise
.then(solution => console.log('Resolved with ' + solution)); // Resolved with 42
如果 onFulfilled 返回了一个 promise,then 的返回值就会被 Promise resolved 或者 rejected。
Promise.prototype.catch()
catch() 方法返回一个Promise,并且处理拒绝的情况。它的行为与调Promise.prototype.then(undefined, onRejected) 相同。 (事实上, calling obj.catch(onRejected) 内部calls obj.then(undefined, onRejected)).
这个需要注意一下,在异步函数中抛出的错误不会被catch捕获到
var p2 = new Promise(function(resolve, reject) {
setTimeout(function() {
throw 'Uncaught Exception!';
}, 1000);
});
p2.catch(function(e) {
console.log(e); // 不会执行
});
Promise.prototype.finally()
finally() 方法返回一个Promise。在promise结束时,无论结果是fulfilled或者是rejected,都会执行指定的回调函数。这为在Promise是否成功完成后都需要执行的代码提供了一种方式。
这避免了同样的语句需要在then和catch中各写一次的情况。
p.finally(function() {
// 返回状态为(resolved 或 rejected)
});
里面不需要传入参数,虽然这个叫finally,但是不代表这个没有返回值,也会返回一个Promise对象
- 由于无法知道
promise的最终状态,所以finally的回调函数中不接收任何参数,它仅用于无论最终结果如何都要执行的情况。 - 与
Promise.resolve(2).then(() => {}, () => {})(resolved的结果为undefined)不同,Promise.resolve(2).finally(() => {})resolved的结果为2。(这个怪怪的) - 同样,
Promise.reject(3).then(() => {}, () => {})(fulfilled的结果为undefined),Promise.reject(3).finally(() => {})rejected 的结果为3。
手写Promise
手写Promise比我想象中的难很多啊,我肯定还是不会写的。具体抄的是这篇文章。
首先是需要定义状态,三个状态,pending、fulfilled、rejected三个状态,状态切换只能从pengding到其他两种状态。
然后需要实现resolve和reject方法,这个不是静态方法那个,而是函数内部的方法。resolve这里面需要考虑传进来的value参数是Promise类型的,需要等到value执行完成
然后就是要直接执行实例化里面的executor函数。
then是最麻烦的,主要要添加异步方法到队列里面,还要处理Promise的情况,递归嵌套,我也很难讲清楚。具体看代码吧。
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
function _Promise(executor) {
const _this = this;
this.status = PENDING; //状态
this.result = undefined; //成功结果
this.onFulfilled = []; //成功回调函数队列
this.onRejected = []; //失败回调函数队列
function resolve(value) {
if (_this.status === PENDING) {
// 如果是Promise类型的,那么就转换成异步的
if (value instanceof _Promise) {
value.then(
(v) => resolve(v),
(r) => reject(r)
);
} else {
_this.status = FULFILLED;
_this.result = value;
_this.onFulfilled.forEach((fn) => fn(value));
}
}
}
function reject(reason) {
if (_this.status === PENDING) {
_this.status = REJECTED;
_this.result = reason;
_this.onRejected.forEach((fn) => fn(reason));
}
}
try {
executor(resolve, reject);
} catch (error) {
console.log('?');
reject(error);
}
}
_Promise.prototype.then = function (onFulfilled, onRejected) {
const _this = this;
// 检查是不是function,如果不是,就给个默认函数
if (typeof onFulfilled !== 'function') onFulfilled = (value) => value;
if (typeof onRejected !== 'function')
onRejected = (reason) => {
throw reason;
};
// 为了能够链式调用then,返回值一定要是_Promise的
return new _Promise((resolve, reject) => {
/**
* 执行onFulfilled(value)和onRejected(reason),得到下一个结果
* @param {onFulfilled|onRejected} callback
* @param {any} result
*/
function handle(callback, result) {
try {
const nextResult = callback(result);
// 如果还是_Promise类型的
if (nextResult instanceof _Promise) {
// 那么等nextRusult执行完成了,再把结果赋值给resolve
nextResult.then(
(v) => resolve(v),
(r) => reject(r)
);
} else {
resolve(nextResult);
}
} catch (err) {
reject(err);
}
}
if (_this.status === FULFILLED) {
/* 状态已经完成了,比如这种直接返回的情况
Promise((resolve, reject) => {
resolve(3);
}).then();
虽然已经成功了,但是也不能直接主线程返回值,要通过异步的方式返回值(原生Promise就是这样的)
*/
setTimeout(() => {
handle(onFulfilled, _this.result);
});
} else if (_this.status === REJECTED) {
setTimeout(() => {
handle(onRejected, _this.result);
});
} else {
// 主线程如果还在Pending,那么就先把回调函数放到队列里面
// 等到主线程运行完,开始运行Promise里面的异步函数
// 就会执行resolve/reject,从而调用队列里面的函数
_this.onFulfilled.push(() => {
handle(onFulfilled, _this.result);
});
_this.onRejected.push(() => {
handle(onRejected, _this.result);
});
}
});
};
// 实例方法catch
_Promise.prototype.catch = function (onRejected) {
return this.then(undefined, onRejected);
};
// 实例方法finally
_Promise.prototype.finally = function (onFinally) {
return this.then(onFinally, onFinally);
};
// 静态方法resolve
_Promise.resolve = function (value) {
return new _Promise((resolve, reject) => {
resolve(value);
});
};
//静态方法resolve
_Promise.reject = function (reason) {
return new _Promise((resolve, reject) => {
reject(reason);
});
};
测试用例,能够处理resove传进去是个Promise类型的例子,按序输出p1 p2
var p1 = new _Promise((resolve, reject) => {
resolve('p1');
});
p1.then((res) => {
console.log(res);
return new _Promise((resolve, reject) => {
resolve(
new _Promise((resolve, reject) => {
resolve('p2');
})
);
});
}).then((res1) => {
console.log(res1);
});
测试🌰2
console.log('开始');
const p = new _Promise((resolve, reject) => {
console.log('输入');
setTimeout(function () {
resolve(1);
}, 1000);
});
p.then(
(value) => {
console.log('1', value);
return 2;
},
(err) => {
console.log('err1', err);
}
)
.then((value) => {
console.log('2', value);
throw 'wrong!';
})
.catch((err) => {
console.log('err2', err);
});
console.log('结束');
输出
开始
输入
结束
1 1
2 2
err2 wrong!
蛋疼的Promise
面试题
1、手撸一个Promise.all()
- [x] 非迭代对象处理(虽然不知道返回啥)
- [x] 空的迭代对象直接同步返回,返回的对象还是Promise
- [x] 如果迭代对象不是Promise,也需要异步处理
- [x] 一旦有错误就返回错误信息
const PromiseAll = function (iterable) {
// 如果是非迭代对象,直接返回
const iterator = Symbol.iterator;
if (!iterable[iterator]) return;
const len = iterable.length;
if (len == 0) return Promise.resolve(iterable);
// 返回对象肯定是个Promise
return new Promise((resolve, reject) => {
// 结果数组
let result = [];
let cnt = 0;
for (let [index, value] of iterable.entries()) {
// 如果不是Promise对象,直接添加
if (!(value instanceof Promise)) {
value = Promise.resolve(value);
/* 本来写成这样子的,但是发现Promise.all
* 传的不是promise数组,比如[1,2],也不是同步返回
* 反而还是在pending,执行过程依然是异步的
* 所以需要套娃一个Promise,变成异步的
result[index] = value;
cnt++;
if (cnt == len) resolve(result);
*/
}
value
.then((res) => {
result[index] = res;
cnt++;
if (cnt == len) resolve(result);
})
.catch((err) => reject(err));
}
});
};
2、trycatch
promise 中的错误能使用 try catch 捕获到吗,不能的话如何实现呢?
不行,try是同步函数的异常,不能用于捕捉异步函数的错误。不能的话就用await来实现。
3、看题说话1
const promise = new Promise((resolve, reject) => {
console.log(1)
resolve()
console.log(2)
})
promise.then(() => {
console.log(3)
})
console.log(4)
注意,promise的参数函数,是被同步执行的,所以输出是1 2 4 3
4、看题说话2
给出一个promise
var promise = new Promise(function(resolve, reject){
setTimeout(function() {
resolve(1);
}, 3000)
})
三种情况有何不同
// 1
promise.then(() => {
return Promise.resolve(2);
}).then((n) => {
console.log(n)
});
// 2
promise.then(() => {
return 2
}).then((n) => {
console.log(n)
});
// 3
promise.then(2).then((n) => {
console.log(n)
});
- 输出2。Promise.resolve 就是一个 Promise 对象就相当于返回了一个新的 Promise 对象。然后在下一个事件循环里才会去执行 then
- 输出2。和上一点不一样的是,它不用等下一个事件循环。
- 输出1。then 和 catch 期望接收函数做参数,如果非函数就会发生 Promise 穿透现象,打印的是上一个 Promise 的返回。
5、看题说话3
不是,怎么有这么变态的题目,真的恶心,直接复制网上的解析。
let a;
const b = new Promise((resolve, reject) => {
console.log('promise1');
resolve();
}).then(() => {
console.log('promise2');
}).then(() => {
console.log('promise3');
}).then(() => {
console.log('promise4');
});
a = new Promise(async (resolve, reject) => {
console.log(a);
await b;
console.log(a);
console.log('after1');
await a
resolve(true);
console.log('after2');
});
console.log('end');
第一个输出 promise1,是因为 Promise 里的方法立即执行。接着调用 resolve,只不过 then 里的方法等下一个周期
第二个输出 undefined,是因为立即执行执行 a 内部的方法,先 console.log(a),但此时的 a 还没赋值给左边的变量,所以只能是 undefined。然后 await b 就得等下一个周期执行了。
第三个输出 end,自然不意外。
接着输出 promise2,promise3,promise4,是因为 await b 等待他执行完了,才轮到 a 内部继续执行。
输出 Promise { pending },脑筋转了以下才想通,事件都进入了循环了,a 肯定已经被赋值成了 Promise 对象。所以第二遍 console.log(a),自然就输出这个了。
输出 after1 不奇怪。
但是随后的 await a 是个什么奇怪的操作,想半天没搞懂为何最后不输出 after2,调试得知根本就执行不到 await a 以后的代码上,想不懂。
更新:和不少朋友交流后,我得出了结论,await a 时,a 是必须等待 Promise 的状态从 pending 到 fullfilled 才会继续往下执行,可 a 的状态是一直得不到更改的,所以无法执行下面的逻辑。只要在 await a 上面加一行 resolve() 就能让后面的 after 2 得到输出。
6、看题说话4
这题我会,只会转移一次状态,所以输出success1
const promise = new Promise((resolve, reject) => {
resolve('success1');
reject('error');
resolve('success2');
});
promise
.then((res) => {
console.log('then: ', res);
})
.catch((err) => {
console.log('catch: ', err);
});
7、看题说话5
这个我也会,会被then处理,因为没有throw错误,即便是Error对象
Promise.resolve()
.then(() => {
return new Error('error!!!')
})
.then((res) => {
console.log('then: ', res)
})
.catch((err) => {
console.log('catch: ', err)
})
8、Promise的执行顺序1
new Promise((resolve) => {
resolve();
Promise.resolve().then(() => console.log(2));
}).then(() => console.log(4));
输出:2 4
里面的Promise.reslove()产生了一个promise1,并且立即settle,所以console.log(2)进入微队列。
外面的构造函数完成返回,产生一个promise2,promise立即settle,所以consol.log(4)进入微队列。
调用栈清空,开始依次执行微任务。
9、Promise的执行顺序2
new Promise((resolve) => {
resolve();
Promise.resolve({
then: function (resolve, reject) {
console.log(1);
resolve();
}
}).then(() => console.log(2));
console.log(0);
}).then(() => console.log(3));
当Promise.resolve()接收一个thenable参数时,即刻产生了一个promise1,并返回这个promise1,这个promise1被 settle的时机是thenable.then的resolve被调用时。
Promise.resolve({then:…})执行完毕后,将thenable.then放入到微任务里面,并返回一个promise1,然后执行console.log(0).
外面那个Promise代码块执行完后,调用栈为空,同时返回一个promise0。
然后promise0立刻settle,把console.log(3)放入到微队列。
然后执行thenable.then里面的console.log(1),resolve后,立刻settle,console.log(2)也放到微任务,然后依次3和2出列。
10、异步并发控制
如何实现一个异步并发控制,promiseConcurrencyLimit(限制并发量,数组,执行函数)
function get(i) {
// console.log('In ', i);
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(i * 1000);
console.log('Out', i, 'Out');
}, 1000);
});
}
const list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// 方法一:使用函数中的局部变量count来保证执行顺序,
function promiseConcurrencyLimit(limit, arr, fn) {
let count = 0;
function run() {
if (count < arr.length) {
fn(arr[count++]).then(() => {
run();
});
}
}
for (let i = 0; i < limit; i++) run();
}
// 方法二:或者使用函数参量来保证,两个都扎不多
function promiseConcurrencyLimit(limit, arr, fn) {
function run(count) {
if (count < arr.length) {
fn(arr[count]).then(() => {
run(count + limit);
});
}
}
for (let i = 0; i < limit; i++) run(i);
}
promiseConcurrencyLimit(3, list, get);
// 执行顺序应该是,纵列表示时间是一致的(基本上),所以第一秒输出123,第二秒输出456,第三秒输出789,最后10
// 1 => 4 => 7 => 10
// 2 => 5 => 8
// 3 => 6 => 9





