原文链接: Promise的奇怪用法和自己实现一个Promise
使用Promise实现一个页面所有图片加载完毕的回调
1import React, { useEffect } from "react"; 2export default () => { 3 useEffect(() => { 4 const imageDomList = Array.from(document.getElementsByClassName("image-item")); 5 6 const imagePromiseList = imageDomList.map( 7 (i) => new Promise((r) => (i.onload = r)) 8 ); 9 const imageLoaded = Promise.all(imagePromiseList); 10 imageLoaded.then(() => { 11 console.log("所有图片加载完成"); 12 }); 13 }, []); 14 15 const imageSrcList = [ 16 "https://oscimg.oschina.net/oscnet/up-3ebf22d4756e0a1935f2549eeb604f69d2f.gif", 17 "https://oscimg.oschina.net/oscnet/up-afc4f6c5faf60f807bed43129a9baaa122a.gif", 18 ]; 19 return ( 20 <div> 21 c1 22 {imageSrcList.map((i) => ( 23 <img class="image-item" key={i} src={i}></img> 24 ))} 25 </div> 26 ); 27};
将resolve函数提出来
仅演示用, 还有的优化空间, 使用context将resolve函数传递到子组件中, 在子组件完成渲染后调用resolve 通知上层组件渲染完毕
1import React, { useEffect, useContext, useState } from "react"; 2 3const resolveMapContext = React.createContext({}); 4 5const Card = ({ id, time }) => { 6 const resolveMap = useContext(resolveMapContext); 7 useEffect(() => { 8 const r = resolveMap[id]; 9 console.log("card", id, r); 10 setTimeout(() => { 11 r && r(id); 12 }, time); 13 }, [resolveMap]); 14 return <div>id:{id}</div>; 15}; 16 17export default () => { 18 const [defaultContext, setDefaultContext] = useState({}); 19 20 const cardList = [ 21 { id: "card1", time: 2000 }, 22 { id: "card2", time: 4000 }, 23 ]; 24 useEffect(() => { 25 console.log("App"); 26 const context = {}; 27 const cardPromiseList = cardList.map( 28 ({ id }) => new Promise((r) => (context[id] = r)) 29 ); 30 31 setDefaultContext(context); 32 const cardRendered = Promise.all(cardPromiseList); 33 cardRendered.then(() => { 34 console.log("所有组件加载完毕"); 35 }); 36 }, []); 37 return ( 38 <resolveMapContext.Provider value={defaultContext}> 39 <div> 40 c1 41 {cardList.map(({ id, time }) => ( 42 <Card key={id} id={id} time={time} /> 43 ))} 44 </div> 45 </resolveMapContext.Provider> 46 ); 47};
上述代码有个问题, 在组件切换的时候, 由于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
1const PENDING = "pending"; 2const FULFILLED = "fulfilled"; 3const REJECTED = "rejected"; 4 5function resolvePromise(promise2, x, resolve, reject) { 6 //PromiseA+ 2.3.1 7 if (promise2 === x) { 8 reject(new TypeError("Chaining cycle")); 9 } 10 if ((x && typeof x === "object") || typeof x === "function") { 11 let used; //PromiseA+2.3.3.3.3 只能调用一次 12 try { 13 let then = x.then; 14 if (typeof then === "function") { 15 //PromiseA+2.3.3 16 then.call( 17 x, 18 (y) => { 19 //PromiseA+2.3.3.1 20 if (used) return; 21 used = true; 22 resolvePromise(promise2, y, resolve, reject); 23 }, 24 (r) => { 25 //PromiseA+2.3.3.2 26 if (used) return; 27 used = true; 28 reject(r); 29 } 30 ); 31 } else { 32 //PromiseA+2.3.3.4 33 if (used) return; 34 used = true; 35 resolve(x); 36 } 37 } catch (e) { 38 //PromiseA+ 2.3.3.2 39 if (used) return; 40 used = true; 41 reject(e); 42 } 43 } else { 44 //PromiseA+ 2.3.3.4 45 resolve(x); 46 } 47} 48 49class Promise { 50 constructor(executor) { 51 this.status = PENDING; 52 this.onFulfilled = []; 53 this.onRejected = []; 54 55 try { 56 executor(this.resolve, this.reject); 57 } catch (e) { 58 this.reject(e); 59 } 60 } 61 resolve = (value) => { 62 if (this.status === PENDING) { 63 this.status = FULFILLED; 64 this.value = value; 65 this.onFulfilled.forEach((fn) => fn()); //PromiseA+ 2.2.6.1 66 } 67 }; 68 69 reject = (reason) => { 70 if (this.status === PENDING) { 71 this.status = REJECTED; 72 this.reason = reason; 73 this.onRejected.forEach((fn) => fn()); //PromiseA+ 2.2.6.2 74 } 75 }; 76 then = (onFulfilled, onRejected) => { 77 //PromiseA+ 2.2.1 / PromiseA+ 2.2.5 / PromiseA+ 2.2.7.3 / PromiseA+ 2.2.7.4 78 onFulfilled = 79 typeof onFulfilled === "function" ? onFulfilled : (value) => value; 80 onRejected = 81 typeof onRejected === "function" 82 ? onRejected 83 : (reason) => { 84 throw reason; 85 }; 86 //PromiseA+ 2.2.7 87 let promise2 = new Promise((resolve, reject) => { 88 if (this.status === FULFILLED) { 89 //PromiseA+ 2.2.2 90 //PromiseA+ 2.2.4 --- setTimeout 91 setTimeout(() => { 92 try { 93 //PromiseA+ 2.2.7.1 94 let x = onFulfilled(this.value); 95 resolvePromise(promise2, x, resolve, reject); 96 } catch (e) { 97 //PromiseA+ 2.2.7.2 98 reject(e); 99 } 100 }); 101 } else if (this.status === REJECTED) { 102 //PromiseA+ 2.2.3 103 setTimeout(() => { 104 try { 105 let x = onRejected(this.reason); 106 resolvePromise(promise2, x, resolve, reject); 107 } catch (e) { 108 reject(e); 109 } 110 }); 111 } else if (this.status === PENDING) { 112 this.onFulfilled.push(() => { 113 setTimeout(() => { 114 try { 115 let x = onFulfilled(this.value); 116 resolvePromise(promise2, x, resolve, reject); 117 } catch (e) { 118 reject(e); 119 } 120 }); 121 }); 122 this.onRejected.push(() => { 123 setTimeout(() => { 124 try { 125 let x = onRejected(this.reason); 126 resolvePromise(promise2, x, resolve, reject); 127 } catch (e) { 128 reject(e); 129 } 130 }); 131 }); 132 } 133 }); 134 return promise2; 135 }; 136 cancel = () => { 137 // console.log("cancel"); 138 this.status = FULFILLED; 139 }; 140} 141 142Promise.defer = Promise.deferred = function () { 143 let dfd = {}; 144 dfd.promise = new Promise((resolve, reject) => { 145 dfd.resolve = resolve; 146 dfd.reject = reject; 147 }); 148 return dfd; 149}; 150 151Promise.resolve = function (param) { 152 if (param instanceof Promise) { 153 return param; 154 } 155 return new Promise((resolve, reject) => { 156 if (param && param.then && typeof param.then === "function") { 157 setTimeout(() => { 158 param.then(resolve, reject); 159 }); 160 } else { 161 resolve(param); 162 } 163 }); 164}; 165 166Promise.reject = function (reason) { 167 return new Promise((resolve, reject) => { 168 reject(reason); 169 }); 170}; 171 172Promise.prototype.catch = function (onRejected) { 173 return this.then(null, onRejected); 174}; 175 176Promise.prototype.finally = function (callback) { 177 return this.then( 178 (value) => { 179 return Promise.resolve(callback()).then(() => { 180 return value; 181 }); 182 }, 183 (err) => { 184 return Promise.resolve(callback()).then(() => { 185 throw err; 186 }); 187 } 188 ); 189}; 190 191Promise.all = function (promises) { 192 return new Promise((resolve, reject) => { 193 let index = 0; 194 let result = []; 195 if (promises.length === 0) { 196 resolve(result); 197 } else { 198 function processValue(i, data) { 199 result[i] = data; 200 if (++index === promises.length) { 201 resolve(result); 202 } 203 } 204 for (let i = 0; i < promises.length; i++) { 205 //promises[i] 可能是普通值 206 Promise.resolve(promises[i]).then( 207 (data) => { 208 processValue(i, data); 209 }, 210 (err) => { 211 reject(err); 212 return; 213 } 214 ); 215 } 216 } 217 }); 218}; 219 220Promise.race = function (promises) { 221 return new Promise((resolve, reject) => { 222 if (promises.length === 0) { 223 return; 224 } else { 225 for (let i = 0; i < promises.length; i++) { 226 Promise.resolve(promises[i]).then( 227 (data) => { 228 resolve(data); 229 return; 230 }, 231 (err) => { 232 reject(err); 233 return; 234 } 235 ); 236 } 237 } 238 }); 239}; 240module.exports = Promise; 241// 跑测试 242// npm install -g promises-aplus-tests 243// promises-aplus-tests promise.js
测试可以取消的方法以及和原生Promise的兼容性
1const CPromise = require("./promise"); 2 3const p = new Promise((r) => 4 setTimeout(() => { 5 r("p"); 6 }, 4000) 7); 8 9const cp = new CPromise((r) => 10 setTimeout(() => { 11 r("cp"); 12 }, 8000) 13); 14p.then((v) => console.log("p then", v, new Date().toTimeString())); 15cp.then((v) => console.log("cp then", v, new Date().toTimeString())); 16Promise.all([p, cp]).then((data) => 17 console.log(data, new Date().toTimeString()) 18); 19 20setTimeout(() => { 21// cp.cancel(); 22}, 2000); 23 24/* 25调用cancel 26p then p 23:51:05 GMT+0800 (China Standard Time) 27 28 29不调用cancel 30p then p 23:51:49 GMT+0800 (China Standard Time) 31cp then cp 23:51:53 GMT+0800 (China Standard Time) 32[ 'p', 'cp' ] 23:51:53 GMT+0800 (China Standard Time) 33*/