{"id":21967080,"url":"https://github.com/barretlee/mypromise","last_synced_at":"2025-07-10T20:04:59.258Z","repository":{"id":21278423,"uuid":"24594339","full_name":"barretlee/myPromise","owner":"barretlee","description":"promise","archived":false,"fork":false,"pushed_at":"2014-09-29T14:25:01.000Z","size":132,"stargazers_count":42,"open_issues_count":0,"forks_count":15,"subscribers_count":4,"default_branch":"gh-pages","last_synced_at":"2025-04-24T05:12:02.760Z","etag":null,"topics":["javascript","promise"],"latest_commit_sha":null,"homepage":"http://barretlee.github.io/myPromise/index.html","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/barretlee.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":"2014-09-29T12:32:33.000Z","updated_at":"2022-11-22T07:00:21.000Z","dependencies_parsed_at":"2022-07-12T15:18:00.302Z","dependency_job_id":null,"html_url":"https://github.com/barretlee/myPromise","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/barretlee/myPromise","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/barretlee%2FmyPromise","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/barretlee%2FmyPromise/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/barretlee%2FmyPromise/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/barretlee%2FmyPromise/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/barretlee","download_url":"https://codeload.github.com/barretlee/myPromise/tar.gz/refs/heads/gh-pages","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/barretlee%2FmyPromise/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":264646934,"owners_count":23643516,"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":["javascript","promise"],"created_at":"2024-11-29T13:21:12.419Z","updated_at":"2025-07-10T20:04:59.198Z","avatar_url":"https://github.com/barretlee.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"## 细嗅Promise\n\nDEMO: [http://barretlee.github.io/myPromise/index.html](http://barretlee.github.io/myPromise/index.html)\n\n### 前言\n\n读完这篇文章，预计会消耗你 40 分钟的时间。\n\nAjax 出现的时候，刮来了一阵异步之风，现在 Nodejs 火爆，又一阵异步狂风刮了过来。需求是越来越苛刻，用户对性能的要求也是越来越高，随之而来的是页面异步操作指数般增长，如果不能恰当的控制代码逻辑，我们就会陷入无穷的回调地狱中。\n\nECMAScript 6 已经将异步操作纳入了规范，现代浏览器也内置了 Promise 对象供我们进行异步编程，那么此刻，还在等啥？赶紧学习学习 Promise 的内部原理吧！\n\n### 第一章 了解 Promise\n\n#### 一、场景再现\n\n由于 javascript 的单线程性质，我们必须等待上一个事件执行完成才能处理下一步，如下：\n\n    // DOM ready之后执行\n    $(document).ready(function(){\n        // 获取模板\n        $.get(url, function(tpl){\n            // 获取数据\n            $.get(url2, function(data){\n                // 构建 DOMString\n                makeHtml(tpl, data, function(str){\n                    // 插入到 DOM 中\n                    $(obj).html(str);\n                });\n            });\n        });\n    });\n\n为了减少首屏数据的加载，我们将一些模板和所有数据都放在服务器端，当用户操作某个按钮时，需要将模板和数据拼接起来插入到 DOM 中，这个过程还必须在 DOMReady 之后才能执行。这种情况是十分常见的，如果异步操作再多一些，整个代码的缩进让人看着很不舒服，为了优雅地处理这个问题，ECMAScript 6 引入了 Promise 的概念，目前一些现代浏览器已经支持这些新东西了！\n\n#### 二、模型\n\n为了让代码流程更加清晰，我们假想着能够按照下面的流程来跑程序：\n\n    new Promise(ready).then(getTpl).then(getData).then(makeHtml).resolve();\n\n先将要事务按照执行顺序依次 push 到事务队列中，push 完了之后再通过 resolve 函数启动整个流程。\n\n整个流程的操作模型如下：\n\n    promise(ok).then(ok_1).then(ok_2).then(ok_3).reslove(value)------+\n             |         |          |          |                       |\n             |         |          |          |        +=======+      |\n             |         |          |          |        |       |      |\n             |         |          |          |        |       |      |\n             +---------|----------|----------|--------→  ok() ←------+\n                       |          |          |        |   ↓   |\n                       |          |          |        |   ↓   |\n                       +----------|----------|--------→ ok_1()|\n                                  |          |        |   ↓   |\n                                  |          |        |   ↓   |\n                                  +----------|--------→ ok_2()|\n                                             |        |   ↓   |\n                                             |        |   ↓   |\n                                             +--------→ ok_3()-----+\n                                                      |       |    |       \n                                                      |       |    ↓\n    @ Created By Barret Lee                           +=======+   exit\n\n\n在 resolve 之前，promise 的每一个 then 都会将回调函数压入队列，resolve 后，将 resolve 的值送给队列的第一个函数，第一个函数执行完毕后，将执行结果再送入下一个函数，依次执行完队列。一连串下来，一气呵成，没有丝毫间断。\n\n#### 三、简单的封装\n\n如果了解 Promise，可以移步下方，看看对 Promise 的封装：\n\nGithub: [https://github.com/barretlee/myPromise](https://github.com/barretlee/myPromise)\n\nDEMO: [http://barretlee.github.io/myPromise/index.html](http://barretlee.github.io/myPromise/index.html)\n\n如果还不是很了解，可以往下阅读全文，了解一二。\n\n### 第二章 Promise 原理\n\n#### 一、什么是 Promise ？\n\n那么，什么是 Promise ？\n\nPromise 可以简单理解为一个事务，这个事务存在三种状态：\n\n1. 已经完成了 resolved\n2. 因为某种原因被中断了 rejected\n3. 还在等待上一个事务结束 pending\n\n上文中我们举了一个栗子，获取模板和数据之后再将拼合的数据插入到 DOM 中，这里我们将整个程序分解成多个事务：\n\n    事务一：     获取模板\n                   ↓\n    事务二：     获取数据\n                   ↓\n    事务三： 拼合之后插入到 DOM\n\n在事务一结束之前，也就是模板代码从服务器拉取过来之前，事务二和事务三都处于 pending 状态，他们必须等待上一个事务结束。而事务一结束之后会将自身状态标记为 resolved，并把该事务中处理的结果移交给事务二继续处理（当然，这里如果没有数据返回，事务二就不会获得上一个事务的数据），依次类推，直到最后一个事务操作结束。\n\n在事务操作的过程中，若遇到错误，比如事务一获取数据存在跨域问题，那事务就会操作失败，此时它会将自身的状态标记为 rejected，由于后续事务都是承接前一事务的，前一事务已经宣告工程已经玩不成了，那么后续的所有事务都会将自己标记为 rejected，其标记理由（reason）就是出错事务的报错信息（这个报错信息可以使用 try...catch 来捕获，也可以通过程序自身来捕获，如 ajax 的 onerror 事件、ajax 返回的状态码为 404 等）。\n\n**小结：Promise 就是一个事务的管理器。他的作用就是将各种内嵌回调的事务用流水形式表达，其目的是为了简化编程，让代码逻辑更加清晰。**\n\n由于整个程序的实现比较难理解，对于 Promise，我们将分为两部分阐述：\n\n- 无错误传递的 Promise，也就是事务不会因为任何原因中断，事务队列中的事项都会被依次处理，此过程中 Promise 只有 pending 和 resolved 两种状态，没有 rejected 状态。\n- 包含错误的 Promise，每个事务的处理都必须使用容错机制来获取结果，一旦出错，就会将错误信息传递给下一个事务，如果错误信息会影响下一个事务，则下一个事务也会 rejected，如果不会，下一个事务可以正常执行，依次类推。\n\n#### 二、无错误传递的 Promise（简化版的 Promise）\n\n首先，我们需要用一个变量（status）来标记事务的状态，然后将事务（affair）也保存到 Promise 对象中。\n\n    var Promise = function(affair){\n        this.state = \"pending\";\n        this.affair = affair || function(o) { return o; };\n        this.allAffairs = [];\n    };\n\nPromise 有两个重要的方法，一个是 then，另一个是 resolve：\n\n- then，将事务添加到事务队列（allAffairs）中\n- resolve，开启流程，让整个操作从第一个事务开始执行\n\n在操作事务之前，我们会先把各种事务依次放入事务队列中，这里会用到 then 方法：\n\n    Promise.prototype.then = function (nextAffair){\n        var promise = new Promise();\n        if (this.state == 'resloved'){\n            // 如果当前状态是已完成，则这个事务将会被立即执行\n            return this._fire(promise, nextAffair);\n        }else{\n            // 否则将会被加入队列中\n            return this._push(promise, nextAffair);\n        }\n    };\n\n如果整个操作已经完成了，那 then 方法送进的事务会被立即执行，\n\n    Promise.prototype._fire = function (nextPromise, nextAffair){\n        var nextResult = nextAffair(this.result);\n        if (nextResult instanceof Promise){\n            nextResult.then(function(obj){\n                nextPromise.resolve(obj);\n            });\n        }else{\n            nextPromise.resolve(nextResult);\n        }\n        return nextPromise;\n    };\n\n被立即执行之后会返回一个结果，这个结果会被传递到下一个事务中作为原料，但是这里需要考虑两种情况：\n\n1. 异步，如果这个结果也是一个 Promise，则需要等待这个 Promise 执行完毕再将最终的结果传到下一个事务中。\n2. 同步，如果这个结果不是 Promise，则直接将结果传递给下一个事务。\n\n第一种情况还是比较常见的，比如我们在一个事务中有一个子事务队列需要处理，此时必须等待子事务完成才能回到主事务队列中。\n\n    Promise.prototype.resolve = function (obj){\n        if (this.state != 'pending') {\n            throw '流程已完成，不能再次开启流程！';\n        }\n        this.state = 'resloved';\n        // 执行该事务，并将执行结果寄存到 Promise 管理器上\n        this.result = this.affair(obj);\n        for (var i = 0, len = this.allAffairs.length; i \u003c len; ++i){\n            // 往后执行事务\n            var affair = this.allAffairs[i];\n            this._fire(affair.promise, affair.affair);\n        }\n        return this;\n    };\n\nresolve 接受一个参数，这个数据是交给第一个事务来处理的，因为第一个事务的启动可能需要点原料，这个数据就是原料，它也可以是空。该事物处理完毕之后，将操作结果（result）寄存在 Promise 对象上，方便引用，然后将结果（result）作为原料送入下一个事务。依次类推。\n\n我们看到 then 方法中还调用了一个 _push ，这个方法的作用是将事务推进事务管理器（Promise）。\n\n    Promise.prototype._push = function (nextPromise, nextAffair){\n        this.allAffairs.push({\n            promise: nextPromise,\n            affair: nextAffair\n        });\n        return nextPromise;\n    };\n\n以上操作，我们就实现了一个简单的事务管理器，可以测试下下面的代码：\n\n    // 初始化事务管理器\n    var promise = new Promise(function(data){\n        console.log(data);\n        return 1;\n    });\n    // 添加事务\n    promise.then(function(data){\n        console.log(data);\n        return 2;\n    }).then(function(data){\n        console.log(data);\n        return 3;\n    }).then(function(data){\n        console.log(data);\n        console.log(\"end\");\n    });\n    // 启动事务\n    promise.resolve(\"start\");\n\n可以看到依次输出的结果为：\n\n    \u003e start\n    \u003e 1\n    \u003e 2\n    \u003e 3\n    \u003e end\n\n由于上述实现十分简陋，链式调用没做太好的处理，请读者自行完善：）\n\n下面是一个异步操作演示：\n\n    var promise = new Promise(function(data){\n        console.log(data);\n        return \"end\";\n    });\n    promise.then(function(data){\n        // 这里需要返回一个 Promise，让主事务切换到子事务处理\n        return (function(data){\n            // 创建一个子事务\n            var promise = new Promise();\n            setTimeout(function(){\n                console.log(data);\n                // 一秒之后才启动子事务，模拟异步延时\n                promise.resolve();\n            }, 1000);\n            return promise;\n        })(data);\n    });\n    promise.resolve(\"start\");\n\n可以看到依次输出的结果为：\n\n    \u003e start\n    \u003e end （1s之后输出）\n\n将函数写的稍微好看点：\n\n    function delay(data){\n        // 创建一个子事务\n        var promise = new Promise();\n        setTimeout(function(){\n            console.log(data);\n            // 一秒之后才启动子事务，模拟异步延时\n            promise.resolve();\n        }, 1000);\n        return promise;\n    }\n    // 主事务\n    var promise = new Promise(function(data){\n        console.log(data);\n        return \"end\";\n    });\n    promise.then(delay);\n    promise.resolve(\"start\");\n\n#### 三、包含错误传递的 Promise\n\n真的很羡慕你能看到这么详细的文章，当然，后面会更加精彩！\n\n没有错误处理的 Promise 只能算是一个半成品，虽说可以通过在最外层加一个 try..catch 来捕获错误，但没法具体定位是哪个事务发生的错误。并且这里的错误不仅仅包含 JavaScript Error，还有诸如 ajax 返回的 data code 不是 200 的情况等。\n\n先看一个浏览器内置 Promise 的实例（该代码可在现代浏览器下运行）：\n\n    new Promise(function(resolve, reject){\n        resolve(\"start\");\n    }).then(function(data){\n        console.log(data);\n        throw \"error\";\n    }).catch(function(err){\n        console.log(err);\n        return \"end\";\n    }).then(function(data){\n        console.log(data)\n    });\n\nPromise 的回调和 then 方法都是接受两个参数：\n\n    new Promise(function(resolve, reject){\n        // ...\n    });\n    \n    promise.then(\n        function(value){/* code here */}, \n        function(reason){/* code here */}\n    );\n\n事务处理过程中，如果有值返回，则作为 value，传入到 resolve 函数中，若有错误产生，则作为 reason 传入到 reject 函数中处理。\n\n在初始化 Promise 对象时，若传入的回调中没有执行 resolve 或者 reject，这需要我们主动去启动事务队列。\n\n    promise.resolve();\n    promise.reject();\n\n上面两种都是可以启动一个队列的。这里跟第二章第二节的 resolve 函数用法类似。Promise 对象还提供了 catch 函数，起用法等价于下面所示：\n\n    promise.catch();\n    // 等价于\n    promise.then(null, function(reason){});\n\n还有两个 API：\n\n    promise.all();\n    promise.race();\n\n后续再讲。先看看这个有错误处理的 Promise 是如何实现的。\n\n    function Promise(resolver){\n        this.status = \"pending\";\n        this.value = null;\n        this.handlers = [];\n        this._doPromise.call(this, resolver);\n    }\n\n_doPromise 方法在实例化 Promise 函数时就执行。如果送入的回调函数 resolver 中已经 resolve 或者 reject 了，程序就已经启动了，所以在实例化的时候就开始判断。\n\n    _doPromise: function(resolver){\n        var called = false, self = this;\n        try{\n            resolver(function(value){\n                // 如果没有 call 则继续，并标记 called 为 true\n                !called \u0026\u0026 (called = !0, self.resolve(value));\n            }, function(reason){\n                // 同上\n                !called \u0026\u0026 (called = !0, self.reject(reason));\n            });\n        } catch(e) {\n            // 同上，捕获错误，传递错误到下一个 then 事务\n            !called \u0026\u0026 (called = !0, self.reject(e));\n        }\n    },\n\n只要 resolve 或者 reject 就会标记程序 called 为 true，表示程序已经启动了。\n\n    resolve: function(value) {\n        try{\n            if(this === value){\n                throw new TypeError('流程已完成，不能再次开启流程！');\n            } else {\n                // 如果还有子事务队列，继续执行\n                value \u0026\u0026 value.then \u0026\u0026 this._doPromise(value.then);\n            }\n            // 执行完了之后标记为完成\n            this.status = \"fulfilled\";\n            this.value = value;\n            this._dequeue();\n        } catch(e) {\n            this.reject(e);\n        }\n    },\n    reject: function(reason) {\n        // 标记状态为出错\n        this.status = \"rejected\";\n        this.value = reason;\n        this._dequeue();\n    },\n\n可以看到，每次 resolve 的时候都会用一个 try..catch 包裹来捕获未知错误。\n\n    _dequeue: function(){\n        var handler;\n        // 执行事务，直到队列为空\n        while (this.handlers.length) {\n            handler = this.handlers.shift();\n            this._handle(handler.thenPromise, handler.onFulfilled, handler.onRejected);\n        }\n    },\n\n无论是 resolve 还是 reject 都会让程序往后奔流，直到结束所有事务，所以这两个方法中都有 _dequeue 函数。\n\n    _handle: function(thenPromise, onFulfilled, onRejected){\n        var self = this;\n    \n        setTimeout(function() {\n            // 判断下次操作采用哪个函数，reject 还是 resolve\n            var callback = self.status == \"fulfilled\" \n                           ? onFulfilled \n                           : onRejected;\n            // 只有是函数才会继续回调\n            if (typeof callback === 'function') {\n                try {\n                    self.resolve.call(thenPromise, callback(self.value));\n                } catch(e) {\n                    self.reject.call(thenPromise, e);\n                }\n                return;\n            }\n            // 否则就将 value 传递给下一个事务了\n            self.status == \"fulfilled\"\n                            ? self.resolve.call(thenPromise, self.value) \n                            : self.reject.call(thenPromise, self.value);\n        }, 1);\n    },\n\n这个函数跟上一节提到的 _fire 类似，如果 callback 是 function，就会进入子事务队列，处理完了之后退回到主事务队列。最后一个 then 方法，将事务推进队列。\n\n    then: function(onFulfilled, onRejected){\n        var thenPromise = new Promise(function() {});\n    \n        if (this.status == \"pending\") {\n            this.handlers.push({\n                thenPromise: thenPromise,\n                onFulfilled: onFulfilled,\n                onRejected: onRejected\n            });\n        } else {\n            this._handle(thenPromise, onFulfilled, onRejected);\n        }\n    \n        return thenPromise;\n    }\n\n如果第二节没有理解清楚，这一节也会让人头疼，这一部分讲的比较粗糙。\n\n### 第三章 异步编程\n\n#### 一、jQuery 中的 Defferred 对象\n\n或许你在面试的时候，有面试官问你：\n\n\u003e `$.ajax()` 执行后返回的结果是什么？\n\n在 jQuery1.5 版本就已经引入了 Defferred 对象，当时为了引入这个东西，整个 jQuery 都被重构了。Defferred 跟 Promise 类似，它表示一个还未完成任务的对象，而 Promise 确切的说，是一个代表未知值的对象。\n\n    $.ajax({\n        url: url\n    }).done(function(data, status, xhr){\n        //...\n    }).fail(function(){\n        //...\n    });\n\n回忆下第二章第一节中的 Promise，是不是如出一辙，只是 jQuery 还提供了更多的语法糖：\n\n    $.ajax({\n        url: url,\n        success: function(data){\n            //...\n        },\n        error: funtion(){\n            //...\n        }    \n    });\n\n他允许将 done 和 fail 两个函数的回调放在 ajax 初始化的参数 success 和 fail 上，其原理还是一样的，同样，还有这样的东西：\n\n    $.when(taskOne, taskTwo).done(function () {\n        console.log(\"都执行完毕后才会输出我！\");\n    }).fail(function(){\n        console.log(\"只要有一个失败，就会输出我！\")\n    });\n\n当 taskOne 和 taskTwo 都完成之后才执行 done 回调，这个浏览器内置的 Promise 也有对应的函数：\n\n    Promise.all([true, Promise.resolve(1), ...]).then(function(value){\n        //....\n    });\n\n浏览器内置的 Promise 还提供了一个 API：\n\n    Promise.race([true, Promise.resolve(1), ...]).then(function(value){\n        //....\n    }, function(reason){\n        //...\n    });\n\n只要 race 参数中有一个 resolve 或者 reject，then 回调就会出发。\n\n\n#### 二、基于事件响应的异步模型\n\n[@朴灵](http://weibo.com/shyvo) 写的 [EventProxy](https://github.com/JacksonTian/eventproxy) 就是基于事件响应的异步模型，按理说，这个实现的逻辑是最清晰的，不过代码量稍微多一点。\n\n    function taskA(){\n        setTimeout(function(){\n            var result = \"A\";\n            E.emit(\"taskA\", result);\n        }, 1000);\n    }\n    \n    function taskB(){\n        setTimeout(function(){\n            var result = \"B\";\n            E.emit(\"taskB\", result);\n        }, 1000);\n    }\n    \n    E.all([\"taskA\", \"taskB\"], function(A, B){\n        return A + B;\n    });\n\n我没有看他的源码，但是想想，应该是这个逻辑。只需要在消息中心管理各个 emit 以及消息注册。这里的错误处理值得思考下。\n\n在半年前，也写过一篇关于异步编程的文章：[JavaScript异步编程原理](http://www.cnblogs.com/hustskyking/p/javascript-asynchronous-programming.html)，感兴趣的可以去读一读。\n\n### 第四章 小结\n\n#### 一、小结\n\n文章比较长，阅读了好几天别人写的东西，自己提笔还是比较轻松的，本文大概花费了 6 个小时撰写。\n\n本文主要解说了 Promise 的应用场景和实现原理，如果你能够顺畅的读完全文并且之处文中的一些错误，说明你已经悟到了：）\n\nPromise 使用起来不难，但是理解其原理还是有点偏头痛的，所以下面列举的几篇相关阅读也建议读者点进去看看。\n\n#### 二、相关阅读\n\n1. [JavaScript Promises](http://www.html5rocks.com/zh/tutorials/es6/promises/)\n2. [Promise 初探](http://mweb.baidu.com/p/promise-introduction.html)\n3. [JavaScript中的异步梳理（0）](http://mweb.baidu.com/p/javascript-async-0.html)\n4. [JavaScript中的异步梳理（2）——使用Promises/A](http://mweb.baidu.com/p/javascript-async-2-promises-a.html)\n5. [jQuery的Deferred对象](http://www.web-tinker.com/article/20154.html)\n6. [JavaScript中的Promise和Deferred对象 第二部分：实战](http://blog.qivhou.com/translation/2013/08/20/promise-deferred-objects-in-javascript-pt2-practical-use/)\n7. [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)\n8. [JavaScript异步编程原理](http://www.cnblogs.com/hustskyking/p/javascript-asynchronous-programming.html)\n\n\n\n\n\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbarretlee%2Fmypromise","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbarretlee%2Fmypromise","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbarretlee%2Fmypromise/lists"}