{"action":"created","issue":{"url":"https://api.github.com/repos/forthealllight/blog/issues/4","repository_url":"https://api.github.com/repos/forthealllight/blog","labels_url":"https://api.github.com/repos/forthealllight/blog/issues/4/labels{/name}","comments_url":"https://api.github.com/repos/forthealllight/blog/issues/4/comments","events_url":"https://api.github.com/repos/forthealllight/blog/issues/4/events","html_url":"https://github.com/forthealllight/blog/issues/4","id":323450836,"node_id":"MDU6SXNzdWUzMjM0NTA4MzY=","number":4,"title":"实现一个完美符合Promise/A+规范的Promise","user":{"login":"forthealllight","id":17233651,"node_id":"MDQ6VXNlcjE3MjMzNjUx","avatar_url":"https://avatars2.githubusercontent.com/u/17233651?v=4","gravatar_id":"","url":"https://api.github.com/users/forthealllight","html_url":"https://github.com/forthealllight","followers_url":"https://api.github.com/users/forthealllight/followers","following_url":"https://api.github.com/users/forthealllight/following{/other_user}","gists_url":"https://api.github.com/users/forthealllight/gists{/gist_id}","starred_url":"https://api.github.com/users/forthealllight/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/forthealllight/subscriptions","organizations_url":"https://api.github.com/users/forthealllight/orgs","repos_url":"https://api.github.com/users/forthealllight/repos","events_url":"https://api.github.com/users/forthealllight/events{/privacy}","received_events_url":"https://api.github.com/users/forthealllight/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":0,"created_at":"2018-05-16T02:51:08Z","updated_at":"2018-07-17T01:45:18Z","closed_at":null,"author_association":"OWNER","body":"简要介绍：Promise允许我们通过链式调用的方式来解决“回调地狱”的问题，特别是在异步过程中，通过Promise可以保证代码的整洁性和可读性。本文主要解读Promise/A+规范，并在此规范的基础上，自己实现一个Promise.\r\n\r\n## 一、Promise的使用\r\n\r\n在了解Promise规范之前，我们知道主流的高版本浏览器已经支持ECMA中的Promise.\r\n\r\n创建一个promise实例：\r\n```\r\nvar p=new Promise(function(resolve,reject){\r\n    setTimeout(function(){\r\n       resolve(\"success\")\r\n    },1000);\r\n    console.log(\"创建一个新的promise\");\r\n})\r\np.then(function(x){\r\n  console.log(x)\r\n})\r\n\r\n//输出：\r\n创建一个新的promise\r\nsuccess\r\n```\r\n上述是一个promise的实例，输出内容为，“创建一个promise”，延迟1000ms后，输出\"success\"。\r\n\r\n从上述的例子可以看出，promise方便处理异步操作。此外promise还可以链式的调用：\r\n\r\n```\r\nvar p=new Promise(function(resolve,reject){resolve()});\r\np.then(...).then(...).then(...)\r\n```\r\n此外Promise除了then方法外，还提供了Promise.resolve、Promise.all、Promise.race等等方法。\r\n\r\n##  二、Promise/A+规范\r\n\r\n[Promise/A+](https://promisesaplus.com/)规范扩展了早期的[Promise/A proposal](http://wiki.commonjs.org/wiki/Promises/A)提案，我们来解读一下Promise/A+规范。\r\n\r\n### 1.术语\r\n\r\n（1）\"promise\"是一个对象或者函数，该对象或者函数有一个then方法\r\n\r\n（2）\"thenable\"是一个对象或者函数，用来定义then方法\r\n\r\n（3）\"value\"是promise状态成功时的值\r\n\r\n（4）\"reason\"是promise状态失败时的值\r\n\r\n我们明确术语的目的，是为了在自己实现promise时，保持代码的规范性（也可以跳过此小节）\r\n\r\n### 2.要求\r\n\r\n（1）一个promise必须有3个状态，pending，fulfilled(resolved)，rejected当处于pending状态的时候，可以转移到fulfilled(resolved)或者rejected状态。当处于fulfilled(resolved)状态或者rejected状态的时候，就不可变。\r\n\r\npromise英文译为承诺，也就是说promise的状态一旦发生改变，就永远是不可逆的。\r\n\r\n（2）一个promise必须有一个then方法，then方法接受两个参数：\r\n\r\n```\r\npromise.then(onFulfilled,onRejected)\r\n```\r\n其中onFulfilled方法表示状态从pending——>fulfilled(resolved)时所执行的方法，而onRejected表示状态从pending——>rejected所执行的方法。\r\n\r\n（3）为了实现链式调用，then方法必须返回一个promise\r\n\r\n```\r\npromise2=promise1.then(onFulfilled,onRejected)\r\n```\r\n\r\n## 三、实现一个符合Promise/A+规范的Promise\r\n\r\n解读了Promise/A+规范之后，下面我们来看如何实现一个Promise,\r\n首先构造一个myPromise函数，关于所有变量和函数名，应该与规范中保持相同。\r\n\r\n### 1.v1.0 初始版本myPromise\r\n```\r\nfunction myPromise(constructor){\r\n    let self=this;\r\n    self.status=\"pending\" //定义状态改变前的初始状态\r\n    self.value=undefined;//定义状态为resolved的时候的状态\r\n    self.reason=undefined;//定义状态为rejected的时候的状态\r\n    function resolve(value){\r\n        //两个===\"pending\"，保证了状态的改变是不可逆的\r\n       if(self.status===\"pending\"){\r\n          self.value=value;\r\n          self.status=\"resolved\";\r\n       }\r\n    }\r\n    function reject(reason){\r\n        //两个===\"pending\"，保证了状态的改变是不可逆的\r\n       if(self.status===\"pending\"){\r\n          self.reason=reason;\r\n          self.status=\"rejected\";\r\n       }\r\n    }\r\n    //捕获构造异常\r\n    try{\r\n       constructor(resolve,reject);\r\n    }catch(e){\r\n       reject(e);\r\n    }\r\n}\r\n```\r\n同时，需要在myPromise的原型上定义链式调用的then方法：\r\n\r\n```\r\nmyPromise.prototype.then=function(onFullfilled,onRejected){\r\n   let self=this;\r\n   switch(self.status){\r\n      case \"resolved\":\r\n        onFullfilled(self.value);\r\n        break;\r\n      case \"rejected\":\r\n        onRejected(self.reason);\r\n        break;\r\n      default:       \r\n   }\r\n}\r\n```\r\n上述就是一个初始版本的myPromise，在myPromise里发生状态改变，然后在相应的then方法里面根据不同的状态可以执行不同的操作。\r\n\r\n```\r\nvar p=new myPromise(function(resolve,reject){resolve(1)});\r\np.then(function(x){console.log(x)})\r\n//输出1\r\n```\r\n但是这里myPromise无法处理异步的resolve.比如：\r\n\r\n```\r\nvar p=new myPromise(function(resolve,reject){setTimeout(function(){resolve(1)},1000)});\r\n\r\np.then(function(x){console.log(x)})\r\n//无输出\r\n```\r\n\r\n### 2.v2.0基于观察模式实现\r\n\r\n为了处理异步resolve，我们修改myPromise的定义，用2个数组onFullfilledArray和onRejectedArray来保存异步的方法。在状态发生改变时，一次遍历执行数组中的方法。\r\n\r\n```\r\nfunction myPromise(constructor){\r\n    let self=this;\r\n    self.status=\"pending\" //定义状态改变前的初始状态\r\n    self.value=undefined;//定义状态为resolved的时候的状态\r\n    self.reason=undefined;//定义状态为rejected的时候的状态\r\n    self.onFullfilledArray=[];\r\n    self.onRejectedArray=[];\r\n    function resolve(value){\r\n       if(self.status===\"pending\"){\r\n          self.value=value;\r\n          self.status=\"resolved\";\r\n          self.onFullfilledArray.forEach(function(f){\r\n                f(self.value);\r\n                //如果状态从pending变为resolved，\r\n                //那么就遍历执行里面的异步方法\r\n          });\r\n        \r\n       }\r\n    }\r\n    function reject(reason){\r\n       if(self.status===\"pending\"){\r\n          self.reason=reason;\r\n          self.status=\"rejected\";\r\n          self.onRejectedArray.forEach(function(f){\r\n              f(self.reason);\r\n             //如果状态从pending变为rejected， \r\n             //那么就遍历执行里面的异步方法\r\n          })\r\n       }\r\n    }\r\n    //捕获构造异常\r\n    try{\r\n       constructor(resolve,reject);\r\n    }catch(e){\r\n       reject(e);\r\n    }\r\n}\r\n```\r\n对于then方法，状态为pending时，往数组里面添加方法：\r\n```\r\nmyPromise.prototype.then=function(onFullfilled,onRejected){\r\n   let self=this;\r\n   switch(self.status){\r\n      case \"pending\":\r\n        self.onFullfilledArray.push(function(){\r\n             onFullfilled(self.value)\r\n        });\r\n        self.onRejectedArray.push(function(){\r\n             onRejected(self.reason)\r\n        });\r\n      case \"resolved\":\r\n        onFullfilled(self.value);\r\n        break;\r\n      case \"rejected\":\r\n        onRejected(self.reason);\r\n        break;\r\n      default:       \r\n   }\r\n}\r\n```\r\n这样，通过两个数组，在状态发生改变之后再开始执行，这样可以处理异步resolve无法调用的问题。这个版本的myPromise就能处理所有的异步，那么这样做就完整了吗？\r\n\r\n没有，我们做Promise/A+规范的最大的特点就是链式调用，也就是说then方法返回的应该是一个promise。\r\n\r\n### 3.v3.0then方法实现链式调用\r\n\r\n要通过then方法实现链式调用，那么也就是说then方法每次调用需要返回一个primise,同时在返回promise的构造体里面，增加错误处理部分，我们来改造then方法\r\n\r\n```\r\nmyPromise.prototype.then=function(onFullfilled,onRejected){\r\n    let self=this;\r\n    let promise2;\r\n    switch(self.status){\r\n      case \"pending\":\r\n        promise2=new myPromise(function(resolve,reject){\r\n             self.onFullfilledArray.push(function(){\r\n                try{\r\n                   let temple=onFullfilled(self.value);\r\n                   resolve(temple)\r\n                }catch(e){\r\n                   reject(e) //error catch\r\n                }\r\n             });\r\n             self.onRejectedArray.push(function(){\r\n                 try{\r\n                   let temple=onRejected(self.reason);\r\n                   reject(temple)\r\n                 }catch(e){\r\n                   reject(e)// error catch\r\n                 }\r\n             });\r\n        })\r\n      case \"resolved\":\r\n        promise2=new myPromise(function(resolve,reject){\r\n            try{\r\n              let temple=onFullfilled(self.value);\r\n              //将上次一then里面的方法传递进下一个Promise的状态\r\n              resolve(temple);\r\n            }catch(e){\r\n              reject(e);//error catch\r\n            }\r\n        })\r\n        break;\r\n      case \"rejected\":\r\n        promise2=new myPromise(function(resolve,reject){\r\n            try{\r\n               let temple=onRejected(self.reason);\r\n               //将then里面的方法传递到下一个Promise的状态里\r\n               resolve(temple);   \r\n            }catch(e){\r\n               reject(e);\r\n            }\r\n        })\r\n        break;\r\n      default:       \r\n   }\r\n   return promise2;\r\n}\r\n```\r\n这样通过then方法返回一个promise就可以实现链式的调用：\r\n\r\n```\r\np.then(function(x){console.log(x)}).then(function(){console.log(\"链式调用1\")}).then(function(){console.log(\"链式调用2\")})\r\n//输出\r\n1\r\n链式调用1\r\n链式调用2\r\n```\r\n这样我们虽然实现了then函数的链式调用，但是还有一个问题，就是在Promise/A+规范中then函数里面的onFullfilled方法和onRejected方法的返回值可以是对象，函数，甚至是另一个promise。\r\n\r\n### 4.v4.0 then函数中的onFullfilled和onRejected方法的返回值问题\r\n\r\n特别的为了解决onFullfilled和onRejected方法的返回值可能是一个promise的问题。\r\n\r\n（1）首先来看promise中对于onFullfilled函数的返回值的要求\r\n\r\nI）如果onFullfilled函数返回的是该promise本身，那么会抛出类型错误\r\n\r\nII）如果onFullfilled函数返回的是一个不同的promise，那么执行该promise的then函数，在then函数里将这个promise的状态转移给新的promise\r\nIII）如果返回的是一个嵌套类型的promsie，那么需要递归。\r\n\r\nIV)如果返回的是非promsie的对象或者函数，那么会选择直接将该对象或者函数，给新的promise。\r\n\r\n根据上述返回值的要求，我们要重新的定义resolve函数，这里Promise/A+规范里面称为：resolvePromise函数，该函数接受当前的promise、onFullfilled函数或者onRejected函数的返回值、resolve和reject作为参数。\r\n\r\n下面我们来看resolvePromise函数的定义：\r\n\r\n```\r\nfunction resolvePromise(promise,x,resolve,reject){\r\n  if(promise===x){\r\n     throw new TypeError(\"type error\")\r\n  }\r\n  let isUsed;\r\n  if(x!==null&&(typeof x===\"object\"||typeof x===\"function\")){\r\n      try{\r\n        let then=x.then;\r\n        if(typeof then===\"function\"){\r\n           //是一个promise的情况\r\n           then.call(x,function(y){\r\n              if(isUsed)return;\r\n              isUsed=true;\r\n              resolvePromise(promise,y,resolve,reject);\r\n           },function(e){\r\n              if(isUsed)return;\r\n              isUsed=true;\r\n              reject(e);\r\n           })\r\n        }else{\r\n           //仅仅是一个函数或者是对象\r\n           resolve(x)\r\n        }\r\n      }catch(e){\r\n         if(isUsed)return;\r\n         isUsed=true;\r\n         reject(e);\r\n      }\r\n  }else{\r\n    //返回的基本类型，直接resolve\r\n    resolve(x)\r\n  }\r\n}\r\n```\r\n改变了resolvePromise函数之后，我们在then方法里面的调用也变成了resolvePromise而不是promise。\r\n\r\n```\r\nmyPromise.prototype.then=function(onFullfilled,onRejected){\r\n    let self=this;\r\n    let promise2;\r\n    switch(self.status){\r\n      case \"pending\":\r\n        promise2=new myPromise(function(resolve,reject){\r\n             self.onFullfilledArray.push(function(){\r\n                setTimeout(function(){\r\n                  try{\r\n\t                   let temple=onFullfilled(self.value);\r\n\t                   resolvePromise(temple)\r\n\t                }catch(e){\r\n\t                   reject(e) //error catch\r\n\t                }\r\n                })\r\n             });\r\n             self.onRejectedArray.push(function(){\r\n                setTimeout(function(){\r\n                   try{\r\n\t                   let temple=onRejected(self.reason);\r\n\t                   resolvePromise(temple)\r\n\t                 }catch(e){\r\n\t                   reject(e)// error catch\r\n\t               }\r\n                })\r\n             });\r\n        })\r\n      case \"resolved\":\r\n        promise2=new myPromise(function(resolve,reject){\r\n           setTimeout(function(){\r\n               try{\r\n\t              let temple=onFullfilled(self.value);\r\n\t              //将上次一then里面的方法传递进下一个Promise状态\r\n\t              resolvePromise(temple);\r\n\t            }catch(e){\r\n                  reject(e);//error catch\r\n               }\r\n           })\r\n        })\r\n        break;\r\n      case \"rejected\":\r\n        promise2=new myPromise(function(resolve,reject){\r\n           setTimeout(function(){\r\n             try{\r\n               let temple=onRejected(self.reason);\r\n               //将then里面的方法传递到下一个Promise的状态里\r\n               resolvePromise(temple);   \r\n             }catch(e){\r\n               reject(e);\r\n             }\r\n           })\r\n        })\r\n        break;\r\n      default:       \r\n   }\r\n   return promise2;\r\n}\r\n```\r\n这样就能处理onFullfilled各种返回值的情况。\r\n\r\n```\r\nvar p=new Promise(function(resolve,reject){resolve(\"初始化promise\")})\r\np.then(function(){return new Promise(function(resolve,reject){resolve(\"then里面的promise返回值\")})}).then(function(x){console.log(x)})\r\n\r\n//输出\r\nthen里面promise的返回值\r\n```\r\n到这里可能有点乱，我们再理一理，首先返回值有两个：\r\n\r\n - then函数的返回值——>返回一个新promise，从而实现链式调用\r\n \r\n - then函数中的onFullfilled和onRejected方法——>返回基本值或者新的promise\r\n\r\n这两者其实是有关联的，onFullfilled方法的返回值可以决定then函数的返回值。\r\n\r\n##  四、检测是否完全符合Promise/A+规范\r\n\r\n```\r\nnpm install -g promises-aplus-tests \r\n```\r\n具体用法请看[promise test](https://github.com/promises-aplus/promises-tests)然后\r\n\r\n```\r\npromises-aplus-tests  myPromise.js\r\n```\r\n结果为：\r\n\r\n![这里写图片描述](http://img.blog.csdn.net/20180409163059341?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbGl3dXNlbg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)\r\n说明我们的实现完全符合Promise/A+规范。\r\n\r\n完整代码的地址\r\n\r\nhttps://github.com/forthealllight/promise-achieve\r\n\r\n##  五、最后补充Typescript实现的Promise/A+规范（可以忽略此节）\r\n\r\n```\r\ninterface IConstructor{\r\n  (resolve:IResolve,reject:IReject):void\r\n}\r\ninterface IResolve {\r\n    (x:any):void\r\n}\r\ninterface IReject {\r\n    (x:any):void\r\n}\r\nfunction myPromise(constructor:IConstructor):void{\r\n  let self:object=this;\r\n  self.status=\"pending\";\r\n  self.value=undefined;//if pending->resolved\r\n  self.reason=undefined;//if pending->rejected\r\n  self.onFullfilledArray=[];//to deal with async(resolved)\r\n  self.onRejectedArray=[];//to deal with async(rejeced)\r\n  let resolve:IResolve;\r\n  resolve=function(value:any):void{\r\n    //pending->resolved\r\n    if(self.status===\"pending\"){\r\n      self.status=\"resolved\";\r\n      self.value=value;\r\n      self.onFullfilledArray.forEach(function(f){\r\n        f(self.value);\r\n      })\r\n    }\r\n  }\r\n  let reject:IReject;\r\n  reject=function(reason:any):void{\r\n    if(self.status===\"pending\"){\r\n      self.status=\"rejected\";\r\n      self.reason=reason;\r\n      self.onRejectedArray.forEach(function(f){\r\n        f(self.reason);\r\n      })\r\n    }\r\n  }\r\n  //According to the definition that the function \"constructor\" accept two parameters\r\n  //error catch\r\n  try {\r\n    constructor(resolve,reject);\r\n  } catch (e) {\r\n    reject(e);\r\n  }\r\n}\r\n```\r\n单纯的写个工具函数，用ts还是有点影响可读性。"},"comment":{"url":"https://api.github.com/repos/forthealllight/blog/issues/comments/405432794","html_url":"https://github.com/forthealllight/blog/issues/4#issuecomment-405432794","issue_url":"https://api.github.com/repos/forthealllight/blog/issues/4","id":405432794,"node_id":"MDEyOklzc3VlQ29tbWVudDQwNTQzMjc5NA==","user":{"login":"pomelovico","id":11907752,"node_id":"MDQ6VXNlcjExOTA3NzUy","avatar_url":"https://avatars1.githubusercontent.com/u/11907752?v=4","gravatar_id":"","url":"https://api.github.com/users/pomelovico","html_url":"https://github.com/pomelovico","followers_url":"https://api.github.com/users/pomelovico/followers","following_url":"https://api.github.com/users/pomelovico/following{/other_user}","gists_url":"https://api.github.com/users/pomelovico/gists{/gist_id}","starred_url":"https://api.github.com/users/pomelovico/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/pomelovico/subscriptions","organizations_url":"https://api.github.com/users/pomelovico/orgs","repos_url":"https://api.github.com/users/pomelovico/repos","events_url":"https://api.github.com/users/pomelovico/events{/privacy}","received_events_url":"https://api.github.com/users/pomelovico/received_events","type":"User","site_admin":false},"created_at":"2018-07-17T01:45:18Z","updated_at":"2018-07-17T01:45:18Z","author_association":"NONE","body":"请教一个问题，在v4版本中resolvePromise函数需要4个参数，为啥在then方法的实现里只传了一个promise参数呢"}}
{"actor":{"display_login":"pomelovico"}}