原文链接: Promise的奇怪用法和自己实现一个Promise
使用Promise实现一个页面所有图片加载完毕的回调
import React, { useEffect } from "react";
export default () => {
useEffect(() => {
const imageDomList = Array.from(document.getElementsByClassName("image-item"));
const imagePromiseList = imageDomList.map(
(i) => new Promise((r) => (i.onload = r))
);
const imageLoaded = Promise.all(imagePromiseList);
imageLoaded.then(() => {
console.log("所有图片加载完成");
});
}, []);
const imageSrcList = [
"https://oscimg.oschina.net/oscnet/up-3ebf22d4756e0a1935f2549eeb604f69d2f.gif",
"https://oscimg.oschina.net/oscnet/up-afc4f6c5faf60f807bed43129a9baaa122a.gif",
];
return (
<div>
c1
{imageSrcList.map((i) => (
<img class="image-item" key={i} src={i}></img>
))}
</div>
);
};
将resolve函数提出来
仅演示用, 还有的优化空间, 使用context将resolve函数传递到子组件中, 在子组件完成渲染后调用resolve 通知上层组件渲染完毕
import React, { useEffect, useContext, useState } from "react";
const resolveMapContext = React.createContext({});
const Card = ({ id, time }) => {
const resolveMap = useContext(resolveMapContext);
useEffect(() => {
const r = resolveMap[id];
console.log("card", id, r);
setTimeout(() => {
r && r(id);
}, time);
}, [resolveMap]);
return <div>id:{id}</div>;
};
export default () => {
const [defaultContext, setDefaultContext] = useState({});
const cardList = [
{ id: "card1", time: 2000 },
{ id: "card2", time: 4000 },
];
useEffect(() => {
console.log("App");
const context = {};
const cardPromiseList = cardList.map(
({ id }) => new Promise((r) => (context[id] = r))
);
setDefaultContext(context);
const cardRendered = Promise.all(cardPromiseList);
cardRendered.then(() => {
console.log("所有组件加载完毕");
});
}, []);
return (
<resolveMapContext.Provider value={defaultContext}>
<div>
c1
{cardList.map(({ id, time }) => (
<Card key={id} id={id} time={time} />
))}
</div>
</resolveMapContext.Provider>
);
};
上述代码有个问题, 在组件切换的时候, 由于promise不能被取消, 所以我们不能在useEffect的return函数中终止promise的执行,当然可以引入变量实现, 不过有更加优雅的方式
自己实现一个Promise, 提供 cancel方法用于取消promise
https://zhuanlan.zhihu.com/p/58428287
https://github.com/YvetteLau/Blog/issues/2
https://juejin.im/post/6844903625769091079
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
function resolvePromise(promise2, x, resolve, reject) {
//PromiseA+ 2.3.1
if (promise2 === x) {
reject(new TypeError("Chaining cycle"));
}
if ((x && typeof x === "object") || typeof x === "function") {
let used; //PromiseA+2.3.3.3.3 只能调用一次
try {
let then = x.then;
if (typeof then === "function") {
//PromiseA+2.3.3
then.call(
x,
(y) => {
//PromiseA+2.3.3.1
if (used) return;
used = true;
resolvePromise(promise2, y, resolve, reject);
},
(r) => {
//PromiseA+2.3.3.2
if (used) return;
used = true;
reject(r);
}
);
} else {
//PromiseA+2.3.3.4
if (used) return;
used = true;
resolve(x);
}
} catch (e) {
//PromiseA+ 2.3.3.2
if (used) return;
used = true;
reject(e);
}
} else {
//PromiseA+ 2.3.3.4
resolve(x);
}
}
class Promise {
constructor(executor) {
this.status = PENDING;
this.onFulfilled = [];
this.onRejected = [];
try {
executor(this.resolve, this.reject);
} catch (e) {
this.reject(e);
}
}
resolve = (value) => {
if (this.status === PENDING) {
this.status = FULFILLED;
this.value = value;
this.onFulfilled.forEach((fn) => fn()); //PromiseA+ 2.2.6.1
}
};
reject = (reason) => {
if (this.status === PENDING) {
this.status = REJECTED;
this.reason = reason;
this.onRejected.forEach((fn) => fn()); //PromiseA+ 2.2.6.2
}
};
then = (onFulfilled, onRejected) => {
//PromiseA+ 2.2.1 / PromiseA+ 2.2.5 / PromiseA+ 2.2.7.3 / PromiseA+ 2.2.7.4
onFulfilled =
typeof onFulfilled === "function" ? onFulfilled : (value) => value;
onRejected =
typeof onRejected === "function"
? onRejected
: (reason) => {
throw reason;
};
//PromiseA+ 2.2.7
let promise2 = new Promise((resolve, reject) => {
if (this.status === FULFILLED) {
//PromiseA+ 2.2.2
//PromiseA+ 2.2.4 --- setTimeout
setTimeout(() => {
try {
//PromiseA+ 2.2.7.1
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
//PromiseA+ 2.2.7.2
reject(e);
}
});
} else if (this.status === REJECTED) {
//PromiseA+ 2.2.3
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
} else if (this.status === PENDING) {
this.onFulfilled.push(() => {
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
});
this.onRejected.push(() => {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
});
}
});
return promise2;
};
cancel = () => {
// console.log("cancel");
this.status = FULFILLED;
};
}
Promise.defer = Promise.deferred = function () {
let dfd = {};
dfd.promise = new Promise((resolve, reject) => {
dfd.resolve = resolve;
dfd.reject = reject;
});
return dfd;
};
Promise.resolve = function (param) {
if (param instanceof Promise) {
return param;
}
return new Promise((resolve, reject) => {
if (param && param.then && typeof param.then === "function") {
setTimeout(() => {
param.then(resolve, reject);
});
} else {
resolve(param);
}
});
};
Promise.reject = function (reason) {
return new Promise((resolve, reject) => {
reject(reason);
});
};
Promise.prototype.catch = function (onRejected) {
return this.then(null, onRejected);
};
Promise.prototype.finally = function (callback) {
return this.then(
(value) => {
return Promise.resolve(callback()).then(() => {
return value;
});
},
(err) => {
return Promise.resolve(callback()).then(() => {
throw err;
});
}
);
};
Promise.all = function (promises) {
return new Promise((resolve, reject) => {
let index = 0;
let result = [];
if (promises.length === 0) {
resolve(result);
} else {
function processValue(i, data) {
result[i] = data;
if (++index === promises.length) {
resolve(result);
}
}
for (let i = 0; i < promises.length; i++) {
//promises[i] 可能是普通值
Promise.resolve(promises[i]).then(
(data) => {
processValue(i, data);
},
(err) => {
reject(err);
return;
}
);
}
}
});
};
Promise.race = function (promises) {
return new Promise((resolve, reject) => {
if (promises.length === 0) {
return;
} else {
for (let i = 0; i < promises.length; i++) {
Promise.resolve(promises[i]).then(
(data) => {
resolve(data);
return;
},
(err) => {
reject(err);
return;
}
);
}
}
});
};
module.exports = Promise;
// 跑测试
// npm install -g promises-aplus-tests
// promises-aplus-tests promise.js
测试可以取消的方法以及和原生Promise的兼容性
const CPromise = require("./promise");
const p = new Promise((r) =>
setTimeout(() => {
r("p");
}, 4000)
);
const cp = new CPromise((r) =>
setTimeout(() => {
r("cp");
}, 8000)
);
p.then((v) => console.log("p then", v, new Date().toTimeString()));
cp.then((v) => console.log("cp then", v, new Date().toTimeString()));
Promise.all([p, cp]).then((data) =>
console.log(data, new Date().toTimeString())
);
setTimeout(() => {
// cp.cancel();
}, 2000);
/*
调用cancel
p then p 23:51:05 GMT+0800 (China Standard Time)
不调用cancel
p then p 23:51:49 GMT+0800 (China Standard Time)
cp then cp 23:51:53 GMT+0800 (China Standard Time)
[ 'p', 'cp' ] 23:51:53 GMT+0800 (China Standard Time)
*/