{"_id":"@coconilu/mypromise","_rev":"1-ff013cc631a15700b6309da9c8829add","name":"@coconilu/mypromise","dist-tags":{"latest":"1.0.0"},"versions":{"1.0.0":{"name":"@coconilu/mypromise","version":"1.0.0","description":"Promise using setTimeout","main":"index.js","directories":{"test":"test"},"dependencies":{},"devDependencies":{"jest":"^23.6.0"},"scripts":{"test":"jest"},"repository":{"type":"git","url":"git+https://github.com/coconilu/myPromise.git"},"keywords":["Promise"],"author":{"name":"coconilu"},"license":"ISC","bugs":{"url":"https://github.com/coconilu/myPromise/issues"},"homepage":"https://github.com/coconilu/myPromise#readme","gitHead":"6d7bd270b4d5ccaaba94c4abc792cabb8ff9bdc3","_id":"@coconilu/mypromise@1.0.0","_npmVersion":"5.7.1","_nodeVersion":"8.11.4","_npmUser":{"name":"coconilu","email":"alphaisall@163.com"},"dist":{"integrity":"sha512-s6ZQXJE+TcGzj6TL3YxAT0/RZObJkO+yi7gHmUrU2kn3EqOqkWN7bzca6CWcwPzhyNanu3z9drK2lXHzdkEIPQ==","shasum":"071ec48c2cf65290da9ca6f7fe2608d2dc403de3","tarball":"https://registry.npmjs.org/@coconilu/mypromise/-/mypromise-1.0.0.tgz","fileCount":7,"unpackedSize":158623,"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJbtJX8CRA9TVsSAnZWagAACnwP/1aTMk4BpzDF5q4li9/H\n1IuSeSQcFPoVmPhzvobr63oSU01gjEP+ClKQaeeTUj5aWrgGYYrx/Ms36i9x\nRspzOk+s5rsWJkhUa5KBZhrpeUJytC0LmKi4Hecnrhk0k8+M6jd8s4TXrQO2\newcKIkxodB/T8p4CAzYD/1Q2zopxJHEWrPZVZJ5OVCWhUCI6uj1gCqUYmwS/\ngf3I5budw/dcot6Bw7unWUe0SnbVbvGn2YWx9g/GA+Bt3VSbX4sl9ciCP1hw\nLF7XC2fGs2ykrMJ0g+2neT9eeVQrNd6lDZE+mAgHdaBFyw42SAaE6kU1/98p\nKADlI19gYpmx+0U1gv31NyteH7KF0F2sr7RynFcGpNIQJjTiODsgNCaZ8O5E\nuJoc0fabpK/wDJvtHXko1RCV4W6sFDC4R6e28E9XUYF2DwKpDx062BsWpGML\neDNmgxGX7y3NSaEp/LoLF3Uo0a78bxOMhfIDgbbGqSkfTFiggSuW5Uki5BhM\ntBYSO/0/BYML86HROc7gLiaY4ClBIuMyyOoIT0ONVq6oT8H+gCDXA5OQlxL4\nkkDsFRC7AXKwuEl1XYddUYJwZEhR+9f5pVdZ+e1HLtJQCMVMDB2UCqJ9gXpb\n/SIY1CAe7VauclUQSu5W22qCcK/T1xihci95KW8omCAA2NvVD2m/L5PIKf8V\neeBR\r\n=X/er\r\n-----END PGP SIGNATURE-----\r\n","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQCY1pHpYmla3XfVIakp0oii31kdtyuVRHdBCl+XW0DLAQIhAIAgrpKkRHJeGSxoU5uoKPI6juCCtKc80Ol2PeGvAjmh"}]},"maintainers":[{"name":"coconilu","email":"alphaisall@163.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/mypromise_1.0.0_1538561531806_0.5045160751356599"},"_hasShrinkwrap":false}},"time":{"created":"2018-10-03T10:12:11.643Z","1.0.0":"2018-10-03T10:12:12.395Z","modified":"2022-04-04T23:55:08.193Z"},"maintainers":[{"name":"coconilu","email":"alphaisall@163.com"}],"description":"Promise using setTimeout","homepage":"https://github.com/coconilu/myPromise#readme","keywords":["Promise"],"repository":{"type":"git","url":"git+https://github.com/coconilu/myPromise.git"},"author":{"name":"coconilu"},"bugs":{"url":"https://github.com/coconilu/myPromise/issues"},"license":"ISC","readme":"# 介绍MyPromise\r\n\r\nMyPromise是模仿Promise实现的一套异步处理框架，但是MyPromise是使用setTimeout和ES6语法实现的。其中ES6语法是可选的。\r\n\r\n它跟ES6标准实现的Promise是有区别的：\r\n\r\nES6的Promise是使用micro-task-queue，回调在一轮事件循环里就可以被主线程执行；\r\n\r\n而MyPromise由于是使用setTimeout实现的，回调是放在macro-task-queue里，所以会在下一轮事件循环被主线程执行。\r\n\r\n> micro-task-queue和macro-task-queue的介绍可以看我的[另一篇文章](https://github.com/coconilu/Blog/issues/7)\r\n\r\n## 以ES6的Promise为参考\r\n\r\nES6的Promise规范可以[参考MDN](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise)。\r\n\r\nMyPromise将依次实现以下API：\r\n\r\n```Javascript\r\nPromise.prototype.then()\r\nPromise.prototype.catch()\r\nPromise.prototype.finally()\r\nPromise.resolve()\r\nPromise.reject()\r\nPromise.race()\r\nPromise.all()\r\n```\r\n\r\n## 必备的知识\r\n\r\n1. ES6的一些基础语法，如class\r\n2. `Function.prototype.bind()`\r\n3. 闭包\r\n4. setTimeout异步处理思维\r\n\r\n## MyPromise原理\r\n\r\n1. 基于setTimeout实现的异步框架；\r\n2. 存储每一次链式调用的回调；\r\n3. 在发起resolve（成功）或reject（失败）时发起异步处理。\r\n\r\n> 用一张图描述整个业务逻辑：\r\n\r\n![MyPromise原理](https://raw.githubusercontent.com/coconilu/myPromise/master/picture/MyPromise.jpg)\r\n\r\n## 逐个实现API\r\n\r\n完整的代码可以看：[`./src/MyPromise.js`](https://github.com/coconilu/myPromise/blob/master/src/MyPromise.js)\r\n\r\n## 1. MyPromise的构造函数\r\n\r\n首先需要保留MyPromise的状态，有3个状态：pending（未完成状态）、resolved（成功状态）、rejected（失败状态）；\r\n\r\n其次需要存储链式调用中的回调函数（下面称之为`chain-callback`），使用数组来存储它们；\r\n\r\n最后调用构造函数传入进来的实参回调（下面称之为`argument-callback`）。\r\n\r\n代码如下：\r\n\r\n```Javascript\r\nconstructor(fun) {\r\n    this.state = stateEnum.pending\r\n    this.fun = fun\r\n    this.onResolvedArray = [];\r\n    this.onRejectedArray = [];\r\n    try {\r\n        this.fun.call(undefined, resolve.bind(this), reject.bind(this))\r\n    } catch (err) {\r\n        // 处理错误\r\n        reject.bind(this)(err)\r\n    }\r\n}\r\n```\r\n\r\n## 2. 对于resolve和reject函数\r\n\r\n这两个函数才是MyPromise的灵魂所在，我并没有把它们放在构造函数或者构造函数的prototype里是因为不想它被使用者破坏。\r\n这里使用resolve和reject命名是为了跟随潮流，别的命名也是可以的。\r\n先把代码贴出来,由于resolve和reject函数逻辑差不多，这里仅讲解一下resolve：\r\n\r\n```Javascript\r\nconst resolve = function (value) {\r\n    if (this.state === stateEnum.pending) {\r\n        setTimeout(() => {\r\n            let result;\r\n            // 过滤onResolvedArray中undefined的情况，注意避免死循环\r\n            while (!(this.onResolvedArray[0] instanceof Function)) {\r\n                if (!this.onResolvedArray.length) return\r\n                this.onResolvedArray.shift()\r\n                this.onRejectedArray.shift()\r\n            }\r\n            try {\r\n                result = this.onResolvedArray[0].call(undefined, value)\r\n                // 判断返回的是不是一个Promise对象\r\n                if (!(result instanceof MyPromise)) {\r\n                    result = MyPromise.resolve()\r\n                }\r\n                this.state = stateEnum.resolved\r\n            } catch (err) {\r\n                result = MyPromise.reject(err)\r\n            } finally {\r\n                this.onResolvedArray.shift()\r\n                this.onRejectedArray.shift()\r\n                result.fillCallbacks(this.onResolvedArray, this.onRejectedArray)\r\n            }\r\n        })\r\n    }\r\n}\r\n```\r\n\r\n1. 为了防止在`argument-callback`多次调用resolve引起的不正常现象，我们对MyPromise对象的状态进行判断；\r\n2. 当`argument-callback`里调用了resolve之后，除了进行状态判断，还会向定时器线程发起一个异步调用（也就是setTimeout），让它立即把回调放入任务队列（macro-task-queue）中，等待主线程的执行；\r\n3. 等到主线程执行这个回调的时候，会把`chain-callback`中取出一个回调执行，并判断返回的结果是不是另一个MyPromise，如果不是则创造一个没有带值的MyPromise，并把`chain-callback`的剩下回调传给新的MyPromise；\r\n4. 继续等待MyPromise里的`argument-callback`调用resolve方法，回到2。\r\n\r\n这就是**链式调用的关键逻辑**。\r\n\r\n## 3. then、catch、finally\r\n\r\n这三个函数都是为了填充MyPromise对象的`chain-callback`，逻辑很简单。\r\n\r\n```Javascript\r\nthen() {\r\n    this.onResolvedArray.push(arguments[0])\r\n    this.onRejectedArray.push(arguments[1])\r\n    return this\r\n}\r\ncatch () {\r\n    this.onResolvedArray.push(undefined)\r\n    this.onRejectedArray.push(arguments[0])\r\n    return this\r\n}\r\nfinally() {\r\n    this.onResolvedArray.push(arguments[0])\r\n    this.onRejectedArray.push(arguments[0])\r\n    return this\r\n}\r\n```\r\n\r\n## 4. MyPromise.resolve和MyPromise.reject\r\n\r\n这两个方法不同于前面介绍的resolve和reject，它们是MyPromise类上的方法，主要作用是创建一个MyPromise对象并调用`argument-callback`里的resolve或reject：\r\n\r\n```Javascript\r\nstatic resolve(value) {\r\n    if (value instanceof MyPromise) return value\r\n    return new MyPromise((_resolve, _reject) => {\r\n        _resolve(value)\r\n    })\r\n}\r\n\r\nstatic reject(err) {\r\n    return new MyPromise((_resolve, _reject) => {\r\n        _reject(err)\r\n    })\r\n}\r\n```\r\n\r\n## 5. MyPromise.race和MyPromise.all\r\n\r\n到这里，其实已经介绍完MyPromise的主要代码和逻辑。\r\n\r\n而MyPromise.race和MyPromise.all提供了一些特殊的用途，借用MDN的介绍。\r\n\r\n> Promise.race(iterable) 方法返回一个 promise ，并伴随着 promise对象解决的返回值或拒绝的错误原因, 只要 iterable 中有一个 promise 对象\"解决(resolve)\"或\"拒绝(reject)\"。\r\n\r\n```Javascript\r\nstatic race(arr) {\r\n    const paramsArr = Array.prototype.slice.call(arr)\r\n    return new MyPromise((resolve, reject) => {\r\n        paramsArr.map((item, index, arr) => {\r\n            if (!(item instanceof MyPromise)) {\r\n                item = MyPromise.resolve(item)\r\n            }\r\n            item.then(val => {\r\n                resolve(val)\r\n            }, err => {\r\n                reject(err)\r\n            })\r\n        })\r\n    })\r\n}\r\n```\r\n\r\n> Promise.all(iterable) 方法返回一个 Promise 实例，此实例在 iterable 参数内所有的 promise 都“完成（resolved）”或参数中不包含 promise 时回调完成（resolve）；如果参数中  promise 有一个失败（rejected），此实例回调失败（rejecte），失败原因的是第一个失败 promise 的结果。\r\n\r\n```Javascript\r\nstatic all(arr) {\r\n    const results = []\r\n    const paramsArr = Array.prototype.slice.call(arr)\r\n    let numOfPromise = 0\r\n    return new MyPromise((resolve, reject) => {\r\n        paramsArr.map((value, index, arr) => {\r\n            if (value instanceof MyPromise) {\r\n                ++numOfPromise;\r\n                value.then(val => {\r\n                    results[index] = val;\r\n                    --numOfPromise;\r\n                    if (numOfPromise === 0) {\r\n                        resolve(results)\r\n                    }\r\n                }, err => {\r\n                    reject(err)\r\n                })\r\n            } else {\r\n                results[index] = value\r\n            }\r\n        })\r\n    })\r\n}\r\n```\r\n\r\n## 测试\r\n\r\n上面主要介绍了resolve，而reject只是只言片语带过。希望[源码](https://github.com/coconilu/myPromise/blob/master/src/MyPromise.js)和[测试代码](https://github.com/coconilu/myPromise/blob/master/test/MyPromise.test.js)能解答你心中的疑惑。\r\n\r\n> 测试代码使用了`Jest`框架\r\n\r\n## 写在最后\r\n\r\n如果文章哪里写的不对，还望不吝指教。\r\n\r\n若有疑问，可以在issue中给我留言。\r\n\r\n谢谢你能看到这里。","readmeFilename":"README.md"}