{"id":28234619,"url":"https://github.com/zwingz/promise","last_synced_at":"2025-06-12T22:31:37.663Z","repository":{"id":31178732,"uuid":"127126232","full_name":"zWingz/Promise","owner":"zWingz","description":"PromiseA+规范以及实现","archived":false,"fork":false,"pushed_at":"2023-01-03T17:48:24.000Z","size":3168,"stargazers_count":0,"open_issues_count":12,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-05-18T22:14:03.692Z","etag":null,"topics":["analysis","promise"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zWingz.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-03-28T10:50:06.000Z","updated_at":"2019-12-30T15:21:29.000Z","dependencies_parsed_at":"2023-01-14T18:29:48.542Z","dependency_job_id":null,"html_url":"https://github.com/zWingz/Promise","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/zWingz/Promise","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zWingz%2FPromise","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zWingz%2FPromise/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zWingz%2FPromise/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zWingz%2FPromise/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zWingz","download_url":"https://codeload.github.com/zWingz/Promise/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zWingz%2FPromise/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":259541564,"owners_count":22873714,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["analysis","promise"],"created_at":"2025-05-18T22:14:04.865Z","updated_at":"2025-06-12T22:31:37.650Z","avatar_url":"https://github.com/zWingz.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Promise 实现原理\n[![CircleCI](https://img.shields.io/circleci/project/github/zWingz/Promise.svg)](https://circleci.com/gh/zWingz/Promise)\n[![codecov](https://codecov.io/gh/zWingz/Promise/branch/master/graph/badge.svg)](https://codecov.io/gh/zWingz/Promise)\n\n## Promise基本用法\n\n```javascript\nnew Promise(function(resolve, reject) {\n    resolve()\n}).then(function(val) {\n    return val\n}, function(error) {\n    catch(error)\n}).catch(function(error) {\n    catch(error)\n})\n```\n\nPromise对象基本方法是`then`, 而`catch`是`then`的一个变形, 相当于`then(undefined, onReject)`\n\n## 实现过程\n\n根据Promise用法, 我们初步想到需要实现的方法是\n\n- 构造函数\n- resolve函数\n- reject函数\n- then函数\n\n此时Promise原型应为\n\n```javascript\nconst PENDING = 'PENDING'\nconst RESOLVED = 'RESOLVED'\nconst REJECT = 'REJECT'\n\nclass Promise {\n    constructor(func) {}\n    resolve(){}\n    reject(){}\n    then(onReslove, onReject){}\n}\n```\n\n### 根据`Promise/A+规范`(以下简称规范)中所说的\n\n- Promise有三个状态 `PENDING`, `RESOLVED`, `REJECTED`\n- 状态只会从`PENDING`转换到`RESOLVED`或者`REJECTED`其中一个, 并且之后不会再改变\n- 当Promise处于执行态时, 会有一个终值, 并且该值不会再改变\n- 当Promise处于拒绝态时, 会有一个据因, 并且该据因不会再改变\n- 当Promise由PENDING转换为RESOLVED时, 会触发`onResolve`回调, 并且只执行一次\n- 当Promise由PENDING转换为REJECTED时, 会触发`onReject`回调, 并且只执行一次\n- Promise状态的转换时机在于开发者何时调用promise的resolve或者reject函数\n\n```javascript\nclass Promise {\n    constructor(func) {\n        this.value = null // 终值或者据因\n        this.status = PENDING // 状态\n        this.onResolveCallBack = [] // resolved 回调\n        this.onRejectCallBack = [] // rejected 回调\n        try {\n            func(this.resolve.bind(this), this.reject.bind(this))\n        } catch (e) {\n            this.reject(e)\n        }\n    }\n    resolve(val){\n        if(this.status === PENDING) {\n            this.value = val // 设置终值\n            this.status = RESOLVED // 设置状态\n            this.onResolveCallBack.forEach(each =\u003e {\n                each(val) // 执行回调\n            })\n        }\n    }\n    reject(reason){\n        if(this.status === PENDING) {\n            this.value = reason // 设置据因\n            this.status = REJECT // 设置状态\n            this.onRejectCallBack.forEach(each =\u003e {\n                each(reason) // 执行回调\n            })\n        }\n    }\n    then(onReslove, onReject){}\n}\n```\n\n这里可能有人会说Promise应该是一个异步的过程, 在上面代码中并没有看到任何的异步. 比如说: setTimeout。\n\n解答：\n\n其实当创建一个Promise实例的时候，整个过程是同步的。\n\n也就是说\n\n```javascript\nconst ins = new Promise(function(res, rej) {\n    res(10)\n})\nconsole.log(ins)\nconsole.log('after ins')\n\n// 输出\n// Promise {\u003cresolved\u003e: 10}\n// after ins\n\n```\n\n当你执行完这一句， ins的状态会马上变成`RESOLVED`. 说明在构造方法中并没有执行异步操作。如果真的需要异步的话，则需要主动在调用`res`前，加上`setTimeout`来触发异步。\n\n```javascript\nconst ins = new Promise(function(res, rej) {\n    setTimeout(() =\u003e {\n        res(10)\n    })\n})\nconsole.log(ins)\nconsole.log('after ins')\n\n// 输出\n// Promise {\u003cpending\u003e}\n// after ins\n```\n\n### 还有一个`then`方法没有完成. 先看下规范怎么说\n\n- 一个promise必须提供一个`then`方法以访问当前值, 终止和据因\n- then接受两个参数`then(onResolve, onReject)`\n- onResolve和onReject都是可选, 如果不是函数则被忽略\n- onResolve方法在promise执行结束后被调用, 其第一个参数为promise的终值, 被调用次数不超过一次\n- onReject方法在promise被拒绝后被调用, 其第一个参数为promise的据因, 同样被调用次数不超过一次\n- onFulfilled 和 onRejected 只有在执行环境堆栈仅包含平台代码时才可被调用 \n- 如果onResolve和onReject返回一个值x, 则执行 **Promise解决过程**\n- then方法必须返回一个`promise`对象\n\n简单说就是\n\n- 如果promise处于pending, 则将then回调放入promise的回调\b列表中\n- 如果promise处于resolved, 则实行then方法中的onResolve\n- 如果promise处于rejected, 则执行then方法中的onReject\n- then方法要确保onResolve和onReject异步执行\n- onResolve和onReject返回的值都将用来解决下一个promise(后面再讲解)\n- 返回新的promise(注意: 一定是新的promise)\n\n```javascript\nclass Promise() {\n    // ...\n    then(onResolve, onReject){\n        const self = this\n        return new Promise(function(nextResolve, nextReject) {\n            if(self.status === PENDING) {\n                // 加入到任务队列\n                self.onResolveCallback.push(onResolve)\n                self.onRejectCallback.push(onReject)\n            } else if(self.status === RESOLVED) {\n                // 异步执行\n                setTimeout(onResolve, 0, self.value)\n            } else {\n                // 异步执行\n                setTimeout(onReject, 0, self.value)\n            }\n        })\n    }\n}\n```\n\n此时Promise已经可以完成异步操作.\n但是Promise还有一个关键特点是可以链式调用. 目前是还没有实现链式调用这一步.\n具体代码看[promise2.js](https://github.com/zWingz/Promise/blob/master/promise2.js)\n\n### 接下来继续看下规范怎么说\n\nPromise 解决过程\n\n- blablabla 这里比较长\n\n**简单说就是**\n\n`x`为`then`方法中`onResolve`或者`onReject`中返回的值, `promise2`为`then`方法返回的新`promise`.\n\n`promise`的解决过程是一个抽象步骤. 需要输入一个`promise`和一个**值**. 表示为`[[Resolve]](promise, x)`\n\n- 如果`x`和`promise2`相等, 则以`TypeError`为据因拒绝执行promise2\n- 如果`x`为`Promise`实例, 则让`promise2`接受x的状态\n- 如果`x`为`thenable`对象, 则调用其`then`方法\n- 如果都不满足, 则用`x`为参数执行`promise2`\n\n继续修改then方法, 以及添加`resolvePromise`来执行`Promise`解决过程\n\n```javascript\nfunction _isFunction(val) {\n  return typeof val === 'function'\n}\nfunction _isThenable(x) {\n  return _isFunction(x) || (typeof x === 'object' \u0026\u0026 x !== null)\n}\n\n/**\n * Promise 解决过程\n * 如果是thenable对象, 则触发该对象的then方法\n * 如果是一个值, 则直接调用resolve解析这个值\n * @param {Promise}} promise\n * @param {Object} x\n * @param {Function} resolve\n * @param {Function} reject\n */\nfunction resolvePromise(promise, x, resolve, reject) {\n  // 要求每次返回新的promise\n  // 如果返回是当前的promise, 则抛出typeError\n  if (x === promise) {\n    reject(new TypeError('Chaining cycle detected for promise'))\n  }\n  let called = false\n  // 判断是否thenable对象\n  if (_isThenable(x)) {\n    try {\n      const { then } = x\n      if (_isFunction(then)) {\n        then.call(\n          x,\n          val =\u003e {\n            if (!called) {\n              called = true\n              // 如果不断的返回thenable\n              // 则需要不断地递归\n              // 但是实际上不应该不断的返回thenable\n              resolvePromise(promise, val, resolve, reject)\n            }\n          },\n          reason =\u003e {\n            if (!called) {\n              called = true\n              reject(reason)\n            }\n          }\n        )\n      } else {\n        resolve(x)\n      }\n    } catch (e) {\n      if (called) {\n        return\n      }\n      called = true\n      reject(e)\n    }\n  } else {\n    //  非thenable, 则以该值来执行resolve\n    resolve(x)\n  }\n}\nclass Promise() {\n    // ...\n    /**\n   * then方法\n   * @param {Function} [onFulfilled] 前then的resolve函数, 当promise为RESOLVE时,处理当前结果\n   * @param {Function} onRejected 当前then的reject函数, 当promise被REJECT时调用\n   * @returns {Promise}\n   * @memberof Promise\n   */\n  then(onFulfilled, onRejected) {\n    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val =\u003e val\n    onRejected =\n      typeof onRejected === 'function'\n        ? onRejected\n        : err =\u003e {\n            throw err\n          }\n    const self = this\n    // 如果有then方法调用, 则将hasThenHandle设为true\n    // console.log(this);\n    this.hasThenHandle = true\n    /**\n     * 返回一个新的promise, 用于链式调用\n     */\n    const ret = new Promise(function(resolve, reject) {\n      // 用try..catch包裹执行方法\n      const tryCatchWrapper = function(fnc) {\n        return function() {\n          try {\n            fnc()\n          } catch (e) {\n            reject(e)\n          }\n        }\n      }\n      // 封装resolve方法回调\n      const doResolve = tryCatchWrapper(function() {\n        resolvePromise(ret, onFulfilled(self.value), resolve, reject)\n      })\n      // 封装reject方法回调\n      // 如果当前then没有相应的reject回调\n      const doReject = tryCatchWrapper(function() {\n        resolvePromise(ret, onRejected(self.value), resolve, reject)\n      })\n      if (self.status === PENDING) {\n        // 如果当前promise还未执行完毕, 则设置回调\n        self.onResolveCallback.push(doResolve)\n        self.onRejectCallback.push(doReject)\n      } else if (self.status === RESOLVED) {\n        // 如果为RESOLVE, 则异步执行resolve\n        setTimeout(doResolve, 0)\n      } else {\n        // 如果为REJECT, 则异步执行reject\n        setTimeout(doReject, 0)\n      }\n    })\n    return ret\n  }\n}\n```\n\n至此一个`Promise`可以说基本完成了.(完整代码请看[index.js](https://github.com/zWingz/Promise/blob/master/index.js))\n\n\n### 规范外的一些东西\n\n其实规范中定义的是`Promise`的构建和执行过程.\n\n而我们日常用到的却不至于规范中所提到的.\n\n比如\n\n- catch\n- finally\n- Promise.resolve\n- Promise.reject\n- all (未实现)\n- race (未实现)\n\n那接下来就说下关于这部分的实现\n\n#### catch\n\n上面有提到. catch其实是`then(undefined, reject)` 的简写. 所以这里比较简单\n\n``` javascript\nclass Promise() {\n    // ...\n    catch(reject) {\n        // 相当于新加入一个then方法\n        return this.then(undefined, reject)\n    }\n}\n```\n\n#### finally (ES2018引入标准)\n\nfinally函数作用我想大家都应该知道, 就是无论当前promise状态是如何. 都一定会执行回调.\n\nfinally方法中, 不接收任何参数, 所以并不能知道前面的Promise的状态.\n\n同时, 他不会对promise产生影响.总是返回原来的值 所以在`finally`中的操作,应该是与状态无关, 不依赖于promise的执行结果\n\n```javascript\nclass Promise() {\n    // ...\n    finally(fnc = () =\u003e {}) {\n        return this.then(val =\u003e {\n            fnc()\n            return val\n        }, err =\u003e {\n            fnc()\n            throw err\n        })\n    }\n}\n```\n\n#### Promise.resolve和Promise.reject (这里是从ES6入门中看到的定义)\n\n```javascript\n// 调用形式\nPromise.resolve(arg)\nPromise.reject(arg)\n```\n\n- Promise.resolve\n\n    根据arg的不同, 会执行不同的操作\n       - arg为Promise实例, 则原封不动的返回这个实例\n       - arg为thenable对象, 则会将arg转成promise, 并且立即执行`arg.then`方法(并不代表同步, 而是本轮事件循环结束时执行)\n       - arg不满足上述情况, 则返回一个新的Promise实例, 状态为resolved, 终值为arg\n    因此`Promise.resolve`是一个更方便的创建`Promise`实例的方法.\n\n- Promise.reject\n\n    这里就不会区分arg, 而是原封不动的把arg作为据因, 执行后续方法的调用.\n\n实现代码\n\n```javascript\nclass Promise() {\n    // ...\n    /**\n     * Promise.resolve\n     * 将参数转成Promise对象\n     * @static\n     * @param {any} val\n     * @returns {MPromise}\n     * @memberof MPromise\n     */\n    static resolve(x) {\n        // 如果为MPromise实例\n        // 则返回该实例\n        if(x instanceof Promise) {\n            return val\n        } else if(_isThenable(x)) {\n            // 如果为具有then方法的对象\n            // 则转为MPromise对象, 并且执行thenable\n            /**\n             * @example\n             * MPromise.resolve({\n             *      then(res) {\n             *          console.log('do promise')\n             *          res(10)\n             *      }\n             *  })\n             */\n            return new Promise(function(res, rej) {\n                // 执行异步\n                setTimeout(function() {\n                    val.then(res, rej)\n                }, 0)\n            })\n        }\n        // 如果val为一个原始值,或者不具有then方法的对象\n        // 则返回一个新的MPromise对象,状态为resolved\n        /**\n         * @example\n         * MPromise.resolve()\n         */\n        return new Promise(function(res) {res(x)})\n    }\n    /**\n     * reject方法参数会原封不动的作为据因而变成后续方法的参数\n     * 且初始状态为REJECT\n     * 不存在判别thenable\n     * @static\n     * @param {any} reason \n     * @returns \n     * @memberof MPromise\n     */\n    static reject(reason) {\n        /**\n         * @example\n         * MPromise.reject('some error')\n         */\n        return new Promise(function(res, rej) {rej(reason)})\n    }\n}\n```\n\n### 开发过程中遇到其他问题\n\n#### node中的`unhandledRejection`和浏览器中的`Uncaught (in promise)` 提示\n\n在Promise中产生的所有错误都会被Promise吞掉. 当没有相应的错误处理函数时候, node和浏览器分别有不同的表现.\n\n但是这并不是一个新的错误, 因为不能用`try{} catch(){}` 捕获.\n\n所以在浏览器端, 是一个`console.error`的错误提示, 在`node`中, 这个算是一个事件. 具体可以通过`process.on`来监听\n\n```javascript\nprocess.on('unhandledRejection', function (err, p) {\n  throw err;\n});\n```\n\n在编写代码中, 一开始卡在这一步挺久.\n\n由于无法知道promise实例后续是否有相应的错误处理函数.\n\n简单的判断`onReject === undefined` 是不行的.\n\n形如:\n\n```javascript\nPromise.reject(10)\n// 或者\nnew Promise(function(res, rej) {\n    rej(10)\n})\n```\n\n这类是同步执行的, `onReject === undefined` 恒为`true`.\n\n我的做法是给promise实例添加一个`hasThenHandle`的属性, 在`then`方法中将其设为`true`\n\n在`reject`方法中使用`setTimeout`异步判断该值是否为`true`, 如果不是则通过`console.error`抛出提示.\n\n其实在原生Promise中, 抛出的`unhandledRejection` 也是属于异步的.\n\n```javascript\nPromise.reject(10)\nconsole.log('after Promise.reject')\nnew Promise(function(res, rej) {\n    rej(10)\n})\nconsole.log('after new Promise')\n\n// 输出\n// after Promise.reject\n// after new Promise\n// Uncaught (in promise) 10\n// Uncaught (in promise) 10\n```\n\n于是这个问题也能得到很好地解决.\n\n至此完整代码已经结束, 具体看`index.js`.\n\n## 存在的问题\n\n- 由于用的是setTimeout模拟, 所以优先级不能保证高于setTimeout\n    - 浏览器中可以用MessageChannel(macrotask)\n    - node中可以用setImmediate(优先级在某些情况下比setTimeout高一些)\n    - setTimeout和setImmediate在无IO操作下,两者执行顺序不确定,但是在IO操作下,setImmediate比setTimeout优先级高. 且setImmediate只在IE下有效\n\n## 参考\n\n[【翻译】Promises/A+规范](http://www.ituring.com.cn/article/66566)\n\n[ECMAScript 6入门](http://es6.ruanyifeng.com/#docs/promise#Promise-prototype-finally)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzwingz%2Fpromise","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzwingz%2Fpromise","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzwingz%2Fpromise/lists"}