Promise
# 实现一个详细版Promise
const micTrigger = cb => Promise.resolve().then(cb)
class MyPromise {
state = 'pendding'; // 'pendding' | 'fulfilled' | 'rejected'
value = undefined; // 用于保存 executor 执行成功的返回值
reason = undefined; // 用于保存 executor 执行措辞的信息
onResolvedCallbacks = [];
onRejectedCallbacks = [];
constructor(executor) {
try {
executor(this.resolve.bind(this), this.reject.bind(this))
} catch (e) {
this.reject(e)
}
}
resolve(value) {
if (this.state === 'pendding') {
this.state = 'fulfilled'
this.value = value
for (const onFulfilled of this.onResolvedCallbacks) micTrigger(() => onFulfilled(value))
}
}
reject(reason) {
if (this.state === 'pendding') {
this.state = 'rejected'
this.reason = reason
for (const onRejected of this.onRejectedCallbacks) micTrigger(() => onRejected(reason))
}
}
handleCallback(callback, resolve, reject, param) {
// 实现链式调用
try {
const result = callback(param)
// result 返回 promise 时,后面的函数就不受这个 callback 控制了,而是继承返回的 promise 状态。其实很简单,将 resolve, reject 传入 result.then 就可以将状态交由 result 托管了
if (result instanceof MyPromise) result.then(resolve, reject)
else resolve(result)
} catch (e) {
reject(e)
}
}
then(onFulfilled, onRejected) {
if (typeof onFulfilled !== 'function') onFulfilled = value => value
if (typeof onRejected !== 'function')
onRejected = reason => {
throw reason
}
const promise = new MyPromise((resolve, reject) => {
if (this.state === 'pendding') {
this.onResolvedCallbacks.push(this.handleCallback.bind(this, onFulfilled, resolve, reject))
this.onRejectedCallbacks.push(this.handleCallback.bind(this, onRejected, resolve, reject))
} else if (this.state === 'fulfilled') {
// 防止promise的状态变成fulfilled再调用then方法
micTrigger(() => this.handleCallback(onFulfilled, resolve, reject, this.value))
} else {
micTrigger(() => this.handleCallback(onRejected, resolve, reject, this.reason))
}
})
return promise
}
catch(onRejected) {
return this.then(null, onRejected)
}
static resolve(value) {
return new MyPromise(r => r(value))
}
static reject(reason) {
return new MyPromise((_, r) => r(reason))
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# 实现Promise.resolve
实现
resolve
静态方法有三个要点:
- 传参为一个
Promise
, 则直接返回它。 - 传参为一个
thenable
对象,返回的Promise
会跟随这个对象,采用它的最终状态作为自己的状态。 - 其他情况,直接返回以该值为成功状态的
promise
对象。
Promise.resolve = (param) => {
if(param instanceof Promise) return param;
return new Promise((resolve, reject) => {
if(param && param.then && typeof param.then === 'function') {
// param 状态变为成功会调用resolve,将新 Promise 的状态变为成功,反之亦然
param.then(resolve, reject);
}else {
resolve(param);
}
})
}
2
3
4
5
6
7
8
9
10
11
# 实现Promise.reject
Promise.reject
中传入的参数会作为一个reason
原封不动地往下传, 实现如下:
Promise.reject = function (reason) {
return new Promise((resolve, reject) => {
reject(reason);
});
}
2
3
4
5
# 实现 Promise.finally
无论当前
Promise
是成功还是失败,调用finally
之后都会执行finally
中传入的函数,并且将值原封不动的往下传。
// 借用 Promise.resolve方法,使得 callback执行完返回后才会继续向下,
// 将 promise对象的原数据继续向下传递,
// 失败数据则需要抛出供后续catch 使用
Promise.prototype.finally = function(cb) {
return this.then(value => {
cb()
return value
}, err => {
cb()
throw err
})
}
2
3
4
5
6
7
8
9
10
11
12
# 实现 Promise.all
重点
对于
all
方法而言,需要完成下面的核心功能:
接收一个promise
数组,返回一个新promise2
,并发执行数组中的全部promise
- 传入参数为一个空的可迭代对象,则直接进行
resolve
。 - 如果参数中有一个
promise
失败,那么Promise.all
返回的promise
对象失败。 - 在任何情况下,
Promise.all
返回的promise
的完成状态的结果都是一个数组
Promise.all = function(promises) {
return new Promise((resolve, reject) => {
let result = [];
let index = 0;
let len = promises.length;
if(len === 0) {
resolve(result);
return;
}
for(let i = 0; i < len; i++) {
// 为什么不直接 promise[i].then, 因为promise[i]可能不是一个promise
Promise.resolve(promise[i]).then(data => {
result[i] = data;
index++;
if(index === len) resolve(result);
}).catch(err => {
reject(err);
})
}
})
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 实现promise.allsettle
接受的结果与入参时的
promise
实例一一对应,且结果的每一项都是一个对象,告诉你结果和值,对象内都有一个属性叫“status
”,用来明确知道对应的这个promise
实例的状态(fulfilled
或rejected
),fulfilled
时,对象有value
属性,rejected
时有reason
属性,对应两种状态的返回值。
const resolved = Promise.resolve(42);
const rejected = Promise.reject(-1);
const allSettledPromise = Promise.allSettled([resolved, rejected]);
allSettledPromise.then(function (results) {
console.log(results);
});
// [
// { status: 'fulfilled', value: 42 },
// { status: 'rejected', reason: -1 }
// ]
2
3
4
5
6
7
8
9
10
11
12
重要的一点是,他不论接受入参的
promise
本身的状态,会返回所有promise
的结果,但这一点Promise.all
做不到,如果你需要知道所有入参的异步操作的所有结果,或者需要知道这些异步操作是否全部结束,应该使用promise.allSettled()
实现
function allSettled(iterable) {
return new Promise((resolve, reject) => {
function addElementToResult(i, elem) {
result[i] = elem;
elementCount++;
if (elementCount === result.length) {
resolve(result);
}
}
let index = 0;
for (const promise of iterable) {
// Capture the current value of `index`
const currentIndex = index;
promise.then(
(value) => addElementToResult(
currentIndex, {
status: 'fulfilled',
value
}),
(reason) => addElementToResult(
currentIndex, {
status: 'rejected',
reason
}));
index++;
}
if (index === 0) {
resolve([]);
return;
}
let elementCount = 0;
const result = new Array(index);
});
}
// 借助Promise.all实现
if (!Promise.allSettled) {
const rejectHandler = (reason) => ({ status: "rejected", reason });
const resolveHandler = (value) => ({ status: "fulfilled", value });
Promise.allSettled = (promises) =>
Promise.all(
promises.map((promise) =>
Promise.resolve(promise).then(resolveHandler, rejectHandler)
)
// 每个 promise 需要用 Promise.resolve 包裹下
// 以防传递非 promise
);
}
// 使用
const p1 = Promise.resolve(1);
const p2 = Promise.resolve(2);
const p3 = new Promise((resolve, reject) => {
setTimeout(reject, 1000, "three");
});
const promises = [p1, p2, p3];
Promise.allSettled(promises).then(console.log);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# 实现 Promise.race
重点
race
的实现相比之下就简单一些,只要有一个promise
执行完,直接resolve
并停止执行
Promise.race = function (promises) {
return new Promise((resolve, reject) => {
for (const p of promises) {
p.then((value) => {
resolve(value);
}, reject);
}
});
};
2
3
4
5
6
7
8
9
# 实现Promise.any
Promise.any = function(promises) {
return new Promise((resolve, reject) => {
let count = 0
promises.forEach((promise) => {
promise.then(val => {
resolve(val)
}, err => {
count++
if (count === promises.length) {
reject(new AggregateError('All promises were rejected'))
}
})
})
})
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 使用Promise
实现每隔1秒输出1,2,3
const arr = [1, 2, 3];
arr.reduce(
(p, x) =>
p.then(() => new Promise((r) => setTimeout(() => r(console.log(x)), 1000))),
Promise.resolve()
);
2
3
4
5
6
改造一下改成一秒后按顺序输出1,2,3
const arr = [1, 2, 3];
arr.reduce(
(p, x) =>
p.then(new Promise((r) => setTimeout(() => r(console.log(x)), 1000))),
Promise.resolve()
);
// 等价于
Promise.resolve()
.then(() => {
return new Promise(r => {
setTimeout(() => {
r(console.log(1))
}, 1000)
})
})
.then(r => {
return new Promise(r => {
setTimeout(() => {
r(console.log(2))
}, 1000)
})
})
.then(r => {
return new Promise(r => {
setTimeout(() => {
r(console.log(3))
}, 1000)
})
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
p .then
里面的() => new Promise
改成 new Promise
# 循环打印红黄绿
红灯 3s 亮一次,绿灯 1s 亮一次,黄灯 2s 亮一次;如何让三个灯不断交替重复亮灯?
三个亮灯函数:
function red() {
console.log('red');
}
function green() {
console.log('green');
}
function yellow() {
console.log('yellow');
}
const task = (time, action) =>
new Promise((resolve) => {
setTimeout(() => {
action();
resolve();
}, time);
});
const taskRunner = async () => {
await task(3000, red)
await task(2000, green)
await task(2100, yellow)
taskRunner()
}
taskRunner()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 实现mergePromise
函数
实现mergePromise函数,把传进去的数组按顺序先后执行,并且把返回的数据先后放到数组data中。
const time = (timer) => {
return new Promise(resolve => {
setTimeout(() => {
resolve()
}, timer)
})
}
const ajax1 = () => time(2000).then(() => {
console.log(1);
return 1
})
const ajax2 = () => time(1000).then(() => {
console.log(2);
return 2
})
const ajax3 = () => time(1000).then(() => {
console.log(3);
return 3
})
function mergePromise () {
// 在这里写代码
}
mergePromise([ajax1, ajax2, ajax3]).then(data => {
console.log("done");
console.log(data); // data 为 [1, 2, 3]
});
// 要求分别输出
// 1
// 2
// 3
// done
// [1, 2, 3]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
这道题有点类似于Promise.all()
,不过.all()
不需要管执行顺序,只需要并发执行就行了。但是这里需要等上一个执行完毕之后才能执行下一个。
答案
function mergePromise (ajaxArray) {
// 存放每个ajax的结果
const data = [];
let promise = Promise.resolve();
ajaxArray.forEach(ajax => {
// 第一次的then为了用来调用ajax
// 第二次的then是为了获取ajax的结果
promise = promise.then(ajax).then(res => {
data.push(res);
return data; // 把每次的结果返回
})
})
// 最后得到的promise它的值就是data
return promise;
}
// async版
const mergePromise = (ajaxArray) => {
// 在这里实现你的代码
return new Promise(async function (resolve) {
let data = [];
for (let item of ajaxArray) {
let tmp = await item();
data.push(tmp);
}
resolve(data);
});
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 用promise
实现图片异步加载
let imageAsync = (url) => {
return new Promise((resolve, reject) => {
let image = new Image();
image.onload = function () {
img.src = url;
resolve(image);
};
image.onerror = function (err) {
reject(err);
};
});
};
imageAsync("url")
.then((img) => {
console.log("image加载成功");
})
.catch((err) => {
console.log(err);
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 封装异步的fetch
使用async
await
方式来使用
(async () => {
class HttpRequestUtil {
async get(url) {
const res = await fetch(url);
const data = await res.json();
return data;
}
async post(url, data) {
const res = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
const result = await res.json();
return result;
}
async put(url, data) {
const res = await fetch(url, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
data: JSON.stringify(data)
});
const result = await res.json();
return result;
}
async delete(url, data) {
const res = await fetch(url, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json'
},
data: JSON.stringify(data)
});
const result = await res.json();
return result;
}
}
const httpRequestUtil = new HttpRequestUtil();
const res = await httpRequestUtil.get('www.crucials.cn');
console.log(res);
})();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# 实现一个带并发限制的异步调度器 Scheduler
重要
实现一个带并发限制的异步调度器
Scheduler
,保证同时运行的任务最多有两个。完善下面代码中的Scheduler
类,使得以下程序能正确输出。
class Scheduler {
add(promiseCreator) { ... }
// ...
}
const timeout = (time) => new Promise(resolve => {
setTimeout(resolve, time)
})
const scheduler = new Scheduler()
const addTask = (time, order) => {
scheduler.add(() => timeout(time)).then(() => console.log(order))
}
addTask(1000, '1')
addTask(500, '2')
addTask(300, '3')
addTask(400, '4')
// 打印顺序是:2 3 1 4
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Scheduler {
constructor() {
this.taskPool = [];
this.limit = 2;
}
add(promiseCreator) {
return new Promise((resolve, reject) => {
this.taskPool.push(async () => {
await promiseCreator();
resolve();
});
});
}
start() {
while (this.limit-- > 0) {
this.next();
}
}
async next() {
if (this.taskPool.length) {
await this.taskPool.shift()();
this.next();
}
}
}
const timeout = (time) =>
new Promise((resolve) => {
setTimeout(resolve, time);
});
const scheduler = new Scheduler();
const addTask = (time, order) => {
scheduler.add(() => timeout(time)).then(() => console.log(order));
};
addTask(1000, "1");
addTask(500, "2");
addTask(300, "3");
addTask(400, "4");
scheduler.start();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
题目下面是没有scheduler.start();
修改一下
class Scheduler {
constructor() {
this.taskPool = [];
this.limit = 2;
}
add(promiseCreator) {
return new Promise((resolve, reject) => {
this.taskPool.push(async () => {
await promiseCreator();
resolve();
});
setTimeout(this.start.bind(this));
});
}
start() {
while (this.limit-- > 0) {
this.next();
}
}
async next() {
if (this.taskPool.length) {
await this.taskPool.shift()();
this.next();
}
}
}
const timeout = (time) =>
new Promise((resolve) => {
setTimeout(resolve, time);
});
const scheduler = new Scheduler();
const addTask = (time, order) => {
scheduler.add(() => timeout(time)).then(() => console.log(order));
};
addTask(1000, "1");
addTask(500, "2");
addTask(300, "3");
addTask(400, "4");
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# 实现cacheRequest()
,相同资源ajax
只发一次请求
请实现一个cacheRequest
方法,保证当前ajax
请求相同资源时,真实网络层中,实际只发出一次请求(假设已经存在request
方法用于封装ajax
请求)
// 构建Map,用作缓存数据
const dict = new Map()
// 这里简单的把url作为cacheKey
const cacheRquest = (url) => {
if (dict.has(url)) {
return Promise.resolve(dict.get(url))
} else {
// 无缓存,发起真实请求,成功后写入缓存
return request(url).then(res => {
dict.set(url, res)
return res
}).catch(err => Promise.reject(err))
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
有这么一个小概率边缘情况,并发两个或多个相同资源请求时,第一个请求处于pending
的情况下,实际上后面的请求依然会发起,不完全符合题意
可以将promise
缓存起来,然后如果处在pending
状态的时候就拿出来继续请求
const dict = new Map();
const cacheRquest = (url) => {
if (dict.has(url)) {
return dict.get(url)
} else {
const pendingData = request(url)
.then((res) => {
return res;
})
.catch((err) => Promise.reject(err));
dict.set(url, pendingData)
return pendingData
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
# 手写一个singlePipe
实现一个任务没有完成,下一个任务不响应
function singlePipe(promiseFunc) {
}
const promiseFunc = (value) =>
new Promise((resolve) => {
setTimeout(() => {
resolve(value);
}, 1000);
});
const request = singlePipe(promiseFunc);
request(1).then((value) => console.log(value));
request(2).then((value) => console.log(value));
setTimeout(() => request(3).then((value) => console.log(value)), 1000);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
类似上面的那道带并发的异步调度器,我们把class
改造成function
function singlePipe(promiseFunc) {
this.queue = this.queue || [];
this.limit = 1;
this.start = () => {
while (this.limit-- > 0) {
this.next();
}
};
this.next = async () => {
if (this.queue.length) {
await this.queue.shift()();
this.next();
}
};
// 返回一个函数,利用了单例模式,让每一个promiseFunc推进的是同一个数组 queue
return (value) => {
return new Promise((resolve) => {
this.queue.push(async () => {
const res = await promiseFunc(value);
resolve(res);
});
setTimeout(this.start.bind(this));
});
};
}
const promiseFunc = (value) =>
new Promise((resolve) => {
setTimeout(() => {
resolve(value);
}, 1000);
});
const request = singlePipe(promiseFunc);
request(1).then((value) => console.log(value));
request(2).then((value) => console.log(value));
setTimeout(() => request(3).then((value) => console.log(value)), 1000);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# 实现 Promise.retry
,成功后resolve
,可延迟重试n
次,超过n
次数后reject
const isFunction = function (fn) {
return Object.prototype.toString.call(fn).slice(8, -1) === "Function";
};
Promise.retry = (fn, options = {}) => {
if (!isFunction(fn)) throw new Error("fn is not a function");
const { max = 3, delay = 0 } = options;
let curMax = max;
const delayExec = () =>
delay && new Promise((resolve) => setTimeout(resolve, delay));
return new Promise(async (resolve, reject) => {
while (curMax > 0) {
try {
const res = await fn();
resolve(res);
return;
} catch (error) {
await delayExec();
curMax--;
console.warn(`剩余次数${curMax}`);
if (!curMax) reject(error);
}
}
});
};
const resolveData = () =>
new Promise((resolve, reject) => {
setTimeout(
() => (Math.random() > 0.5 ? resolve("成功") : reject(new Error("失败"))),
1000
);
});
(async () => {
try {
const res = await Promise.retry(resolveData, { delay: 1000 });
console.warn("result", res);
} catch (error) {
console.warn(error);
}
})();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# Promise
延迟执行
function job(){
return function(){}
}
let myTodo = job(30000, 5)
myTodo('alert')
2
3
4
5
function job(delay, time) {
return function (word) {
const delayPrint = () =>
new Promise((res) =>
setTimeout(() => {
res(console.log(word));
}, delay)
);
let promise = new Promise(async (resolve, reject) => {
while (time--) {
try {
await delayPrint();
resolve();
} catch (error) {
if (!time) {
reject(error);
}
}
}
});
return promise;
};
}
let myTodo = job(1000, 5);
myTodo("alert");
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 输出结果;如果希望每隔 1s
输出一个结果,应该如何改造?注意不可改动 square
方法
const list = [1, 2, 3]
const square = num => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(num * num)
}, 1000)
})
}
function test() {
list.forEach(async x=> {
const res = await square(x)
console.log(res)
})
}
test()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1s
后同时打印 1
、4
、9
原因:使用 promise
或 async
函数作为 forEach()
等类似方法的 callback
参数并不会等待异步的执行
如果我们希望每隔 1s
输出一个结果,方法有:
- 一个简单的
for
循环 for…of
/for…in
循环- 利用
promise
的链式调用
解法一:for
循环
async function test() {
for(let i = 0; i<list.length; i++) {
const res = await square(list[i])
console.log(res)
}
}
test()
2
3
4
5
6
7
解法二:for…in / for…of
// for...in
async function test() {
for(let i in list) {
const res = await square(list[i])
console.log(res)
}
}
test()
// for...of
async function test() {
for(let x of list) {
const res = await square(x)
console.log(res)
}
}
test()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
解法三:利用 promise
的链式调用
function test() {
let promise = Promise.resolve()
list.forEach(x=> {
promise = promise.then(() => square(x)).then(console.log)
})
}
test()
2
3
4
5
6
7