├── readme-source └── images │ ├── 1.png │ ├── 10.png │ ├── 2.png │ ├── 3.png │ ├── 4.jpeg │ ├── 5.png │ ├── 6.png │ ├── 7.png │ ├── 8.png │ └── 9.png ├── lib ├── form-urlencoded.js └── promise.js └── README.md /readme-source/images/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuwanli/octopus-wxapp/HEAD/readme-source/images/1.png -------------------------------------------------------------------------------- /readme-source/images/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuwanli/octopus-wxapp/HEAD/readme-source/images/10.png -------------------------------------------------------------------------------- /readme-source/images/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuwanli/octopus-wxapp/HEAD/readme-source/images/2.png -------------------------------------------------------------------------------- /readme-source/images/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuwanli/octopus-wxapp/HEAD/readme-source/images/3.png -------------------------------------------------------------------------------- /readme-source/images/4.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuwanli/octopus-wxapp/HEAD/readme-source/images/4.jpeg -------------------------------------------------------------------------------- /readme-source/images/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuwanli/octopus-wxapp/HEAD/readme-source/images/5.png -------------------------------------------------------------------------------- /readme-source/images/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuwanli/octopus-wxapp/HEAD/readme-source/images/6.png -------------------------------------------------------------------------------- /readme-source/images/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuwanli/octopus-wxapp/HEAD/readme-source/images/7.png -------------------------------------------------------------------------------- /readme-source/images/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuwanli/octopus-wxapp/HEAD/readme-source/images/8.png -------------------------------------------------------------------------------- /readme-source/images/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuwanli/octopus-wxapp/HEAD/readme-source/images/9.png -------------------------------------------------------------------------------- /lib/form-urlencoded.js: -------------------------------------------------------------------------------- 1 | // Filename: formurlencoded.js 2 | // Timestamp: 2016.03.07-12:29:28 (last modified) 3 | // Author(s): Bumblehead (www.bumblehead.com), JBlashill (james@blashill.com), Jumper423 (jump.e.r@yandex.ru) 4 | // 5 | // http://www.w3.org/TR/html5/forms.html#url-encoded-form-data 6 | // input: {one:1,two:2} return: '[one]=1&[two]=2' 7 | 8 | module.exports = function (data, opts) { 9 | "use strict"; 10 | 11 | // ES5 compatible version of `/[^ !'()~\*]/gu`, https://mothereff.in/regexpu 12 | var encodechar = new RegExp([ 13 | '(?:[\0-\x1F"-&\+-\}\x7F-\uD7FF\uE000-\uFFFF]|', 14 | '[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|', 15 | '(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])' 16 | ].join(''), 'g'); 17 | 18 | opts = typeof opts === 'object' ? opts : {}; 19 | 20 | function encode(value) { 21 | return String(value) 22 | .replace(encodechar, encodeURIComponent) 23 | .replace(/ /g, '+') 24 | .replace(/[!'()~\*]/g, function (ch) { 25 | return '%' + ch.charCodeAt().toString(16).slice(-2).toUpperCase(); 26 | }); 27 | } 28 | 29 | function keys(obj) { 30 | var itemsKeys = Object.keys(obj); 31 | 32 | return opts.sorted ? itemsKeys.sort() : itemsKeys; 33 | } 34 | 35 | function filterjoin(arr) { 36 | return arr.filter(function (e) { 37 | return e; 38 | }).join('&'); 39 | } 40 | 41 | function objnest(name, obj) { 42 | return filterjoin(keys(obj).map(function (key) { 43 | return nest(name + '[' + key + ']', obj[key]); 44 | })); 45 | } 46 | 47 | function arrnest(name, arr) { 48 | return arr.length ? filterjoin(arr.map(function (elem, index) { 49 | return nest(name + '[' + index + ']', elem); 50 | })) : encode(name + '[]'); 51 | } 52 | 53 | function nest(name, value) { 54 | var type = typeof value, 55 | f = null; 56 | 57 | if (value === f) { 58 | f = opts.ignorenull ? f : encode(name) + '=' + f; 59 | } else if (/string|number|boolean/.test(type)) { 60 | f = encode(name) + '=' + encode(value); 61 | } else if (Array.isArray(value)) { 62 | f = arrnest(name, value); 63 | } else if (type === 'object') { 64 | f = objnest(name, value); 65 | } 66 | 67 | return f; 68 | } 69 | 70 | return data && filterjoin(keys(data).map(function (key) { 71 | return nest(key, data[key]); 72 | })); 73 | }; 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 章鱼活动平台 小程序 2 | ========================== 3 | 4 | 章鱼活动平台,浙江本地活动的优选平台。 5 | 6 | 章鱼平台包含的功能很多,包括活动的创建与发布、优质的活动的筛选、活动的报名与审核、自定义门票、线下扫码签到等一系列功能。 7 | 8 | 本人主要负责章鱼活动[pc端用户主页部分](https://github.com/yuwanli/zhangyu-pc)、小程序的开发及后期的h5版的迭代与升级。 9 | 10 | 这里主要是介绍章鱼小程序开发的总结与一些心得,希望能与大家分享(很想把源码推上来的,由于是公司项目,有保密协议没法直接推上来,对源码有兴趣或者有需要可留言私密我)。 11 | 12 | ![章鱼小程序](readme-source/images/4.jpeg "章鱼小程序") 13 | 14 | ### 项目基础框架选型 [wxpage](https://github.com/tvfe/wxpage) 15 | >WXPage 是一个极其轻量的微信小程序开发框架,其中的API蕴含了“极致页面打开速度的思想”,为可维护性与开发效率而设计的功能,框架来自“腾讯视频”小程序的项目沉淀。 16 | 17 | wxpage是在2016年年底去上海参加微信小程序的公开课的时候了解到的,当时就对作者封装的onNavigate还是有onPreload特别感兴趣,本人毕竟只是前端菜鸟,所以作者的那些开发理念和做法确实是挺颠覆我的,值得学习和借鉴的地方有很多,所以这里墙裂推荐大家使用。 18 | 19 | 用作者的说法是,用这种方式可以做到'零秒打开页面',无空白等待的情况,这里大致分析一下: 20 | 21 | 小程序不同于h5的重要一点是,小程序的js是运行在JSCore(安卓x5内核)中的,所以这也是小程序可以原生媲美的原因之一,也是小程序之所以能称为"应用"的原因之一。这样就导致用户打开应用的时候,所有的页面的其实是算启动了,这个时候页面之前是可以进行通信的。(不知道这么解释正不正确啊,毕竟还是菜鸟,希望有大牛帮忙补充) 22 | 23 | 所以可以做的就是提前加载,上一个页面加载下一个页面的数据,然后保存起来,下一个页面先去取,能取到就无须再去请求,若没取到(网络原因或其他原因导致没存储进来),则请求数据即可。 24 | 25 | 拿我开发的这个小程序来说,列表页点击进入详情是一个常见的情况: 26 | 27 | 1,用户点击的时候去请求数据(这个时候还没跳转到详情),这个时候会触发目标页面的onNavigate(扩展的生命周期),此时对应的叶页面并未被加载,这个时候去请求数据然后通过$put,存储起来 28 | 29 | 2,页面onLoad的时候先去$take数据,若没取到则再去请求数据 30 | 31 | ![onNavigate](readme-source/images/1.png "onNavigate") 32 | 33 | 当然,wxpage不仅仅只有这些,它还有对页面、组件的扩展,还有页面生命周期的扩展、自定义的函数属性等等,大家可以参考wxpage的git主页,有兴趣也可以去读下源码。 34 | 35 | ### [项目的开发工具](https://www.egret.com/products/wing.html) 36 | 可以先吐槽下微信官方提供的小程序开发者工具,当然现在大家看到的开发者工具算好很多,但是还是有很多有缺陷的地方,可是我是在小程序内测阶段的时候就开始玩小程序了,那更不用说内测阶段时候的开发者工具了。当然,吐槽归吐槽,作为开发者要有一个包容的心,毕竟他们在不断的完善开发者工具,总是会越来越好。 37 | 38 | 这里,给大家介绍一款方便开发小程序的工具,不是说这个工具有多好,只能说能方便大家的开发。 39 | 40 | 这个是白鹭时代(egret) 下面的一款产品(wing),去网站看你会发现这个主要是游戏编辑器,但是也支持小程序的开发。不得不说白鹭时代,很了不起,有一系列优秀的产品,有一套游戏开发的解决方案,主要是国产的,对!国产的!虽然并不是专业的游戏开发者,但是对他们的产品有些些了解,也参加过他们组织的小程序分享会,觉得还是很腻害的一家公司。 41 | 42 | 为何选择使用wing: 43 | 44 | 1,可自定义快捷键,这是件很酷的事情,想想就很酷; 45 | 46 | ![wing](readme-source/images/2.png "wing") 47 | 2,配合官方的开发者工具一起使用,最好是分屏,wing负责代码的编辑,官方开发者工具负责看效果和调试,这样就无须在开发者工具中来回切换。 48 | 49 | ![wing](readme-source/images/3.png "wing") 50 | 51 | 3,可以像很多编辑器那样split 52 | 53 | ![wing](readme-source/images/5.png "wing") 54 | 55 | 以上三点就可以让你像平时开发h5一样开发小程序,大家可以先试着用下开发者工具,大概就能了解我说的那些痛了。 56 | 57 | ### 项目的管理 58 | 59 | ![manage](readme-source/images/6.png "manage") 60 | 61 | comps:顾名思义,components组件。我会把项目中通用的部分模块化,组件化,如通用底部、loading、章鱼形象、搜索框、门票、列表等。 62 | 63 | libs:用到的一些库 64 | 65 | * [wxParse](https://github.com/icindy/wxParse):用于富文本解析。小程序不支持html里面的标签,所以这里需要对富文本的内容进行解析,生成能运行在小程序里的代码。感谢这样的人民工程师,不然富文本解析这个让我一个菜鸟来解决的话,估计得哭晕在厕所吧。wxParse的作者是[icindy](https://github.com/icindy/),不好意思之前记错了/捂脸。 66 | 67 | * promise:小程序支持es6的大部分语法,起初是支持promise的,忘了是因为啥后来不支持,所以这里得手动引入promise。 68 | 69 | * [wxqrcode](https://kazuhikoarase.github.io/qrcode-generator/):因项目需要把一串码生成一个二维码用户线下扫码。 70 | 71 | * wxpage:前面说的项目基础框架 72 | 73 | pages:项目所涉及到的页面 74 | 75 | resource:用户放设计稿及项目相关文档,便于项目开发。 76 | 77 | service:个人的习惯,把所有的接口放在一个api.js文件进行管理,这里还把自定义的公用函数放在里面,然后还会有一个文件,里面放的是项目的所有接口请求,所有的成功通过回调的方式暴露出来。 78 | 79 | ![api.js](readme-source/images/7.png "api.js") 80 | ![index.js](readme-source/images/8.png "index.js") 81 | 82 | source:存放静态资源 83 | 84 | 85 | ### 关于tab切换 86 | 87 | tab切换:很普通的需求,点击tab切换,下面的列表数据切换成对应的数据,如下图 88 | 89 | ![tab](readme-source/images/9.png "tab") 90 | 91 | 可扫下面码体验: 92 | 93 | ![zhangyu](readme-source/images/10.png "zhangyu") 94 | 95 | 可能大部分能想到的就是和这位前端想到的处理方式一样吧,切换就切换,无非是换个标签,切换一个tab,page从就1重新开始。可是如果用户反复的来回切换呢?之前请求的数据再请求一遍?第一个tab已经请求了第三个page了,然后切换到第二个,然后再切换回来呢,滚动条的位置这么办? 96 | 97 | 我的做法是这样的: 98 | 99 | 每一个标签对应一个对象,对象包含的数据有page、scrollTop、ifMore、active等属于当前标签的字段 100 | 101 | *active:标志当前标签是否被选中,标志位(用于区分标签选中前后样式) 102 | 103 | *ifMore:列表请求回数据时,若长度小于默认的pageNum,则可判断无很多数据,标志位,用于显示'无更多数据',及减少无必要的请求 104 | 105 | *scrollTop:添加一个scrollListen事件,记录各自移动的位置,切换时直接定位到上次浏览的位置,提示用户体验 106 | 107 | *page:存该标签当前所处的page,避免重置成1,重复请求 108 | 109 | 对应的效果,大家可以进入小程序体验下,对应着h5,可以看出明显的差别。 110 | 111 | ![章鱼小程序](readme-source/images/4.jpeg "章鱼小程序") 112 | 113 | ### 项目总结 114 | 115 | 此次小程序的开发,独立完成,且审核一次通过,算是比较成功的吧。后来想了想原因,应该是项目初期对于框架的选择和项目结构的搭建及细节的把握都做了比较足的工作,这让我想起以前写作文的时候,老师总让我们先列提纲,我觉得拿到我们项目的开发中也是有道理的。先收集各种相关资料,选定框架、理清自己的项目结构、项目中可能的难点、可优化的地方等等,总之不是上来就开始写代码。此次开发,对我成长有很大的帮助。 -------------------------------------------------------------------------------- /lib/promise.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * @overview es6-promise - a tiny implementation of Promises/A+. 3 | * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald) 4 | * @license Licensed under MIT license 5 | * See https://raw.githubusercontent.com/stefanpenner/es6-promise/master/LICENSE 6 | * @version 4.1.0+f9a5575b 7 | */ 8 | 9 | (function (global, factory) { 10 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : 11 | typeof define === 'function' && define.amd ? define(factory) : 12 | (global.ES6Promise = factory()); 13 | }(this, (function () { 'use strict'; 14 | 15 | function objectOrFunction(x) { 16 | return typeof x === 'function' || typeof x === 'object' && x !== null; 17 | } 18 | 19 | function isFunction(x) { 20 | return typeof x === 'function'; 21 | } 22 | 23 | var _isArray = undefined; 24 | if (!Array.isArray) { 25 | _isArray = function (x) { 26 | return Object.prototype.toString.call(x) === '[object Array]'; 27 | }; 28 | } else { 29 | _isArray = Array.isArray; 30 | } 31 | 32 | var isArray = _isArray; 33 | 34 | var len = 0; 35 | var vertxNext = undefined; 36 | var customSchedulerFn = undefined; 37 | 38 | var asap = function asap(callback, arg) { 39 | queue[len] = callback; 40 | queue[len + 1] = arg; 41 | len += 2; 42 | if (len === 2) { 43 | // If len is 2, that means that we need to schedule an async flush. 44 | // If additional callbacks are queued before the queue is flushed, they 45 | // will be processed by this flush that we are scheduling. 46 | if (customSchedulerFn) { 47 | customSchedulerFn(flush); 48 | } else { 49 | scheduleFlush(); 50 | } 51 | } 52 | }; 53 | 54 | function setScheduler(scheduleFn) { 55 | customSchedulerFn = scheduleFn; 56 | } 57 | 58 | function setAsap(asapFn) { 59 | asap = asapFn; 60 | } 61 | 62 | var browserWindow = typeof window !== 'undefined' ? window : undefined; 63 | var browserGlobal = browserWindow || {}; 64 | var BrowserMutationObserver = browserGlobal.MutationObserver || browserGlobal.WebKitMutationObserver; 65 | var isNode = typeof self === 'undefined' && typeof process !== 'undefined' && ({}).toString.call(process) === '[object process]'; 66 | 67 | // test for web worker but not in IE10 68 | var isWorker = typeof Uint8ClampedArray !== 'undefined' && typeof importScripts !== 'undefined' && typeof MessageChannel !== 'undefined'; 69 | 70 | // node 71 | function useNextTick() { 72 | // node version 0.10.x displays a deprecation warning when nextTick is used recursively 73 | // see https://github.com/cujojs/when/issues/410 for details 74 | return function () { 75 | return process.nextTick(flush); 76 | }; 77 | } 78 | 79 | // vertx 80 | function useVertxTimer() { 81 | if (typeof vertxNext !== 'undefined') { 82 | return function () { 83 | vertxNext(flush); 84 | }; 85 | } 86 | 87 | return useSetTimeout(); 88 | } 89 | 90 | function useMutationObserver() { 91 | var iterations = 0; 92 | var observer = new BrowserMutationObserver(flush); 93 | var node = document.createTextNode(''); 94 | observer.observe(node, { characterData: true }); 95 | 96 | return function () { 97 | node.data = iterations = ++iterations % 2; 98 | }; 99 | } 100 | 101 | // web worker 102 | function useMessageChannel() { 103 | var channel = new MessageChannel(); 104 | channel.port1.onmessage = flush; 105 | return function () { 106 | return channel.port2.postMessage(0); 107 | }; 108 | } 109 | 110 | function useSetTimeout() { 111 | // Store setTimeout reference so es6-promise will be unaffected by 112 | // other code modifying setTimeout (like sinon.useFakeTimers()) 113 | var globalSetTimeout = setTimeout; 114 | return function () { 115 | return globalSetTimeout(flush, 1); 116 | }; 117 | } 118 | 119 | var queue = new Array(1000); 120 | function flush() { 121 | for (var i = 0; i < len; i += 2) { 122 | var callback = queue[i]; 123 | var arg = queue[i + 1]; 124 | 125 | callback(arg); 126 | 127 | queue[i] = undefined; 128 | queue[i + 1] = undefined; 129 | } 130 | 131 | len = 0; 132 | } 133 | 134 | function attemptVertx() { 135 | try { 136 | var r = require; 137 | var vertx = r('vertx'); 138 | vertxNext = vertx.runOnLoop || vertx.runOnContext; 139 | return useVertxTimer(); 140 | } catch (e) { 141 | return useSetTimeout(); 142 | } 143 | } 144 | 145 | var scheduleFlush = undefined; 146 | // Decide what async method to use to triggering processing of queued callbacks: 147 | if (isNode) { 148 | scheduleFlush = useNextTick(); 149 | } else if (BrowserMutationObserver) { 150 | scheduleFlush = useMutationObserver(); 151 | } else if (isWorker) { 152 | scheduleFlush = useMessageChannel(); 153 | } else if (browserWindow === undefined && typeof require === 'function') { 154 | scheduleFlush = attemptVertx(); 155 | } else { 156 | scheduleFlush = useSetTimeout(); 157 | } 158 | 159 | function then(onFulfillment, onRejection) { 160 | var _arguments = arguments; 161 | 162 | var parent = this; 163 | 164 | var child = new this.constructor(noop); 165 | 166 | if (child[PROMISE_ID] === undefined) { 167 | makePromise(child); 168 | } 169 | 170 | var _state = parent._state; 171 | 172 | if (_state) { 173 | (function () { 174 | var callback = _arguments[_state - 1]; 175 | asap(function () { 176 | return invokeCallback(_state, child, callback, parent._result); 177 | }); 178 | })(); 179 | } else { 180 | subscribe(parent, child, onFulfillment, onRejection); 181 | } 182 | 183 | return child; 184 | } 185 | 186 | /** 187 | `Promise.resolve` returns a promise that will become resolved with the 188 | passed `value`. It is shorthand for the following: 189 | 190 | ```javascript 191 | let promise = new Promise(function(resolve, reject){ 192 | resolve(1); 193 | }); 194 | 195 | promise.then(function(value){ 196 | // value === 1 197 | }); 198 | ``` 199 | 200 | Instead of writing the above, your code now simply becomes the following: 201 | 202 | ```javascript 203 | let promise = Promise.resolve(1); 204 | 205 | promise.then(function(value){ 206 | // value === 1 207 | }); 208 | ``` 209 | 210 | @method resolve 211 | @static 212 | @param {Any} value value that the returned promise will be resolved with 213 | Useful for tooling. 214 | @return {Promise} a promise that will become fulfilled with the given 215 | `value` 216 | */ 217 | function resolve(object) { 218 | /*jshint validthis:true */ 219 | var Constructor = this; 220 | 221 | if (object && typeof object === 'object' && object.constructor === Constructor) { 222 | return object; 223 | } 224 | 225 | var promise = new Constructor(noop); 226 | _resolve(promise, object); 227 | return promise; 228 | } 229 | 230 | var PROMISE_ID = Math.random().toString(36).substring(16); 231 | 232 | function noop() {} 233 | 234 | var PENDING = void 0; 235 | var FULFILLED = 1; 236 | var REJECTED = 2; 237 | 238 | var GET_THEN_ERROR = new ErrorObject(); 239 | 240 | function selfFulfillment() { 241 | return new TypeError("You cannot resolve a promise with itself"); 242 | } 243 | 244 | function cannotReturnOwn() { 245 | return new TypeError('A promises callback cannot return that same promise.'); 246 | } 247 | 248 | function getThen(promise) { 249 | try { 250 | return promise.then; 251 | } catch (error) { 252 | GET_THEN_ERROR.error = error; 253 | return GET_THEN_ERROR; 254 | } 255 | } 256 | 257 | function tryThen(then, value, fulfillmentHandler, rejectionHandler) { 258 | try { 259 | then.call(value, fulfillmentHandler, rejectionHandler); 260 | } catch (e) { 261 | return e; 262 | } 263 | } 264 | 265 | function handleForeignThenable(promise, thenable, then) { 266 | asap(function (promise) { 267 | var sealed = false; 268 | var error = tryThen(then, thenable, function (value) { 269 | if (sealed) { 270 | return; 271 | } 272 | sealed = true; 273 | if (thenable !== value) { 274 | _resolve(promise, value); 275 | } else { 276 | fulfill(promise, value); 277 | } 278 | }, function (reason) { 279 | if (sealed) { 280 | return; 281 | } 282 | sealed = true; 283 | 284 | _reject(promise, reason); 285 | }, 'Settle: ' + (promise._label || ' unknown promise')); 286 | 287 | if (!sealed && error) { 288 | sealed = true; 289 | _reject(promise, error); 290 | } 291 | }, promise); 292 | } 293 | 294 | function handleOwnThenable(promise, thenable) { 295 | if (thenable._state === FULFILLED) { 296 | fulfill(promise, thenable._result); 297 | } else if (thenable._state === REJECTED) { 298 | _reject(promise, thenable._result); 299 | } else { 300 | subscribe(thenable, undefined, function (value) { 301 | return _resolve(promise, value); 302 | }, function (reason) { 303 | return _reject(promise, reason); 304 | }); 305 | } 306 | } 307 | 308 | function handleMaybeThenable(promise, maybeThenable, then$$) { 309 | if (maybeThenable.constructor === promise.constructor && then$$ === then && maybeThenable.constructor.resolve === resolve) { 310 | handleOwnThenable(promise, maybeThenable); 311 | } else { 312 | if (then$$ === GET_THEN_ERROR) { 313 | _reject(promise, GET_THEN_ERROR.error); 314 | GET_THEN_ERROR.error = null; 315 | } else if (then$$ === undefined) { 316 | fulfill(promise, maybeThenable); 317 | } else if (isFunction(then$$)) { 318 | handleForeignThenable(promise, maybeThenable, then$$); 319 | } else { 320 | fulfill(promise, maybeThenable); 321 | } 322 | } 323 | } 324 | 325 | function _resolve(promise, value) { 326 | if (promise === value) { 327 | _reject(promise, selfFulfillment()); 328 | } else if (objectOrFunction(value)) { 329 | handleMaybeThenable(promise, value, getThen(value)); 330 | } else { 331 | fulfill(promise, value); 332 | } 333 | } 334 | 335 | function publishRejection(promise) { 336 | if (promise._onerror) { 337 | promise._onerror(promise._result); 338 | } 339 | 340 | publish(promise); 341 | } 342 | 343 | function fulfill(promise, value) { 344 | if (promise._state !== PENDING) { 345 | return; 346 | } 347 | 348 | promise._result = value; 349 | promise._state = FULFILLED; 350 | 351 | if (promise._subscribers.length !== 0) { 352 | asap(publish, promise); 353 | } 354 | } 355 | 356 | function _reject(promise, reason) { 357 | if (promise._state !== PENDING) { 358 | return; 359 | } 360 | promise._state = REJECTED; 361 | promise._result = reason; 362 | 363 | asap(publishRejection, promise); 364 | } 365 | 366 | function subscribe(parent, child, onFulfillment, onRejection) { 367 | var _subscribers = parent._subscribers; 368 | var length = _subscribers.length; 369 | 370 | parent._onerror = null; 371 | 372 | _subscribers[length] = child; 373 | _subscribers[length + FULFILLED] = onFulfillment; 374 | _subscribers[length + REJECTED] = onRejection; 375 | 376 | if (length === 0 && parent._state) { 377 | asap(publish, parent); 378 | } 379 | } 380 | 381 | function publish(promise) { 382 | var subscribers = promise._subscribers; 383 | var settled = promise._state; 384 | 385 | if (subscribers.length === 0) { 386 | return; 387 | } 388 | 389 | var child = undefined, 390 | callback = undefined, 391 | detail = promise._result; 392 | 393 | for (var i = 0; i < subscribers.length; i += 3) { 394 | child = subscribers[i]; 395 | callback = subscribers[i + settled]; 396 | 397 | if (child) { 398 | invokeCallback(settled, child, callback, detail); 399 | } else { 400 | callback(detail); 401 | } 402 | } 403 | 404 | promise._subscribers.length = 0; 405 | } 406 | 407 | function ErrorObject() { 408 | this.error = null; 409 | } 410 | 411 | var TRY_CATCH_ERROR = new ErrorObject(); 412 | 413 | function tryCatch(callback, detail) { 414 | try { 415 | return callback(detail); 416 | } catch (e) { 417 | TRY_CATCH_ERROR.error = e; 418 | return TRY_CATCH_ERROR; 419 | } 420 | } 421 | 422 | function invokeCallback(settled, promise, callback, detail) { 423 | var hasCallback = isFunction(callback), 424 | value = undefined, 425 | error = undefined, 426 | succeeded = undefined, 427 | failed = undefined; 428 | 429 | if (hasCallback) { 430 | value = tryCatch(callback, detail); 431 | 432 | if (value === TRY_CATCH_ERROR) { 433 | failed = true; 434 | error = value.error; 435 | value.error = null; 436 | } else { 437 | succeeded = true; 438 | } 439 | 440 | if (promise === value) { 441 | _reject(promise, cannotReturnOwn()); 442 | return; 443 | } 444 | } else { 445 | value = detail; 446 | succeeded = true; 447 | } 448 | 449 | if (promise._state !== PENDING) { 450 | // noop 451 | } else if (hasCallback && succeeded) { 452 | _resolve(promise, value); 453 | } else if (failed) { 454 | _reject(promise, error); 455 | } else if (settled === FULFILLED) { 456 | fulfill(promise, value); 457 | } else if (settled === REJECTED) { 458 | _reject(promise, value); 459 | } 460 | } 461 | 462 | function initializePromise(promise, resolver) { 463 | try { 464 | resolver(function resolvePromise(value) { 465 | _resolve(promise, value); 466 | }, function rejectPromise(reason) { 467 | _reject(promise, reason); 468 | }); 469 | } catch (e) { 470 | _reject(promise, e); 471 | } 472 | } 473 | 474 | var id = 0; 475 | function nextId() { 476 | return id++; 477 | } 478 | 479 | function makePromise(promise) { 480 | promise[PROMISE_ID] = id++; 481 | promise._state = undefined; 482 | promise._result = undefined; 483 | promise._subscribers = []; 484 | } 485 | 486 | function Enumerator(Constructor, input) { 487 | this._instanceConstructor = Constructor; 488 | this.promise = new Constructor(noop); 489 | 490 | if (!this.promise[PROMISE_ID]) { 491 | makePromise(this.promise); 492 | } 493 | 494 | if (isArray(input)) { 495 | this._input = input; 496 | this.length = input.length; 497 | this._remaining = input.length; 498 | 499 | this._result = new Array(this.length); 500 | 501 | if (this.length === 0) { 502 | fulfill(this.promise, this._result); 503 | } else { 504 | this.length = this.length || 0; 505 | this._enumerate(); 506 | if (this._remaining === 0) { 507 | fulfill(this.promise, this._result); 508 | } 509 | } 510 | } else { 511 | _reject(this.promise, validationError()); 512 | } 513 | } 514 | 515 | function validationError() { 516 | return new Error('Array Methods must be provided an Array'); 517 | }; 518 | 519 | Enumerator.prototype._enumerate = function () { 520 | var length = this.length; 521 | var _input = this._input; 522 | 523 | for (var i = 0; this._state === PENDING && i < length; i++) { 524 | this._eachEntry(_input[i], i); 525 | } 526 | }; 527 | 528 | Enumerator.prototype._eachEntry = function (entry, i) { 529 | var c = this._instanceConstructor; 530 | var resolve$$ = c.resolve; 531 | 532 | if (resolve$$ === resolve) { 533 | var _then = getThen(entry); 534 | 535 | if (_then === then && entry._state !== PENDING) { 536 | this._settledAt(entry._state, i, entry._result); 537 | } else if (typeof _then !== 'function') { 538 | this._remaining--; 539 | this._result[i] = entry; 540 | } else if (c === Promise) { 541 | var promise = new c(noop); 542 | handleMaybeThenable(promise, entry, _then); 543 | this._willSettleAt(promise, i); 544 | } else { 545 | this._willSettleAt(new c(function (resolve$$) { 546 | return resolve$$(entry); 547 | }), i); 548 | } 549 | } else { 550 | this._willSettleAt(resolve$$(entry), i); 551 | } 552 | }; 553 | 554 | Enumerator.prototype._settledAt = function (state, i, value) { 555 | var promise = this.promise; 556 | 557 | if (promise._state === PENDING) { 558 | this._remaining--; 559 | 560 | if (state === REJECTED) { 561 | _reject(promise, value); 562 | } else { 563 | this._result[i] = value; 564 | } 565 | } 566 | 567 | if (this._remaining === 0) { 568 | fulfill(promise, this._result); 569 | } 570 | }; 571 | 572 | Enumerator.prototype._willSettleAt = function (promise, i) { 573 | var enumerator = this; 574 | 575 | subscribe(promise, undefined, function (value) { 576 | return enumerator._settledAt(FULFILLED, i, value); 577 | }, function (reason) { 578 | return enumerator._settledAt(REJECTED, i, reason); 579 | }); 580 | }; 581 | 582 | /** 583 | `Promise.all` accepts an array of promises, and returns a new promise which 584 | is fulfilled with an array of fulfillment values for the passed promises, or 585 | rejected with the reason of the first passed promise to be rejected. It casts all 586 | elements of the passed iterable to promises as it runs this algorithm. 587 | 588 | Example: 589 | 590 | ```javascript 591 | let promise1 = resolve(1); 592 | let promise2 = resolve(2); 593 | let promise3 = resolve(3); 594 | let promises = [ promise1, promise2, promise3 ]; 595 | 596 | Promise.all(promises).then(function(array){ 597 | // The array here would be [ 1, 2, 3 ]; 598 | }); 599 | ``` 600 | 601 | If any of the `promises` given to `all` are rejected, the first promise 602 | that is rejected will be given as an argument to the returned promises's 603 | rejection handler. For example: 604 | 605 | Example: 606 | 607 | ```javascript 608 | let promise1 = resolve(1); 609 | let promise2 = reject(new Error("2")); 610 | let promise3 = reject(new Error("3")); 611 | let promises = [ promise1, promise2, promise3 ]; 612 | 613 | Promise.all(promises).then(function(array){ 614 | // Code here never runs because there are rejected promises! 615 | }, function(error) { 616 | // error.message === "2" 617 | }); 618 | ``` 619 | 620 | @method all 621 | @static 622 | @param {Array} entries array of promises 623 | @param {String} label optional string for labeling the promise. 624 | Useful for tooling. 625 | @return {Promise} promise that is fulfilled when all `promises` have been 626 | fulfilled, or rejected if any of them become rejected. 627 | @static 628 | */ 629 | function all(entries) { 630 | return new Enumerator(this, entries).promise; 631 | } 632 | 633 | /** 634 | `Promise.race` returns a new promise which is settled in the same way as the 635 | first passed promise to settle. 636 | 637 | Example: 638 | 639 | ```javascript 640 | let promise1 = new Promise(function(resolve, reject){ 641 | setTimeout(function(){ 642 | resolve('promise 1'); 643 | }, 200); 644 | }); 645 | 646 | let promise2 = new Promise(function(resolve, reject){ 647 | setTimeout(function(){ 648 | resolve('promise 2'); 649 | }, 100); 650 | }); 651 | 652 | Promise.race([promise1, promise2]).then(function(result){ 653 | // result === 'promise 2' because it was resolved before promise1 654 | // was resolved. 655 | }); 656 | ``` 657 | 658 | `Promise.race` is deterministic in that only the state of the first 659 | settled promise matters. For example, even if other promises given to the 660 | `promises` array argument are resolved, but the first settled promise has 661 | become rejected before the other promises became fulfilled, the returned 662 | promise will become rejected: 663 | 664 | ```javascript 665 | let promise1 = new Promise(function(resolve, reject){ 666 | setTimeout(function(){ 667 | resolve('promise 1'); 668 | }, 200); 669 | }); 670 | 671 | let promise2 = new Promise(function(resolve, reject){ 672 | setTimeout(function(){ 673 | reject(new Error('promise 2')); 674 | }, 100); 675 | }); 676 | 677 | Promise.race([promise1, promise2]).then(function(result){ 678 | // Code here never runs 679 | }, function(reason){ 680 | // reason.message === 'promise 2' because promise 2 became rejected before 681 | // promise 1 became fulfilled 682 | }); 683 | ``` 684 | 685 | An example real-world use case is implementing timeouts: 686 | 687 | ```javascript 688 | Promise.race([ajax('foo.json'), timeout(5000)]) 689 | ``` 690 | 691 | @method race 692 | @static 693 | @param {Array} promises array of promises to observe 694 | Useful for tooling. 695 | @return {Promise} a promise which settles in the same way as the first passed 696 | promise to settle. 697 | */ 698 | function race(entries) { 699 | /*jshint validthis:true */ 700 | var Constructor = this; 701 | 702 | if (!isArray(entries)) { 703 | return new Constructor(function (_, reject) { 704 | return reject(new TypeError('You must pass an array to race.')); 705 | }); 706 | } else { 707 | return new Constructor(function (resolve, reject) { 708 | var length = entries.length; 709 | for (var i = 0; i < length; i++) { 710 | Constructor.resolve(entries[i]).then(resolve, reject); 711 | } 712 | }); 713 | } 714 | } 715 | 716 | /** 717 | `Promise.reject` returns a promise rejected with the passed `reason`. 718 | It is shorthand for the following: 719 | 720 | ```javascript 721 | let promise = new Promise(function(resolve, reject){ 722 | reject(new Error('WHOOPS')); 723 | }); 724 | 725 | promise.then(function(value){ 726 | // Code here doesn't run because the promise is rejected! 727 | }, function(reason){ 728 | // reason.message === 'WHOOPS' 729 | }); 730 | ``` 731 | 732 | Instead of writing the above, your code now simply becomes the following: 733 | 734 | ```javascript 735 | let promise = Promise.reject(new Error('WHOOPS')); 736 | 737 | promise.then(function(value){ 738 | // Code here doesn't run because the promise is rejected! 739 | }, function(reason){ 740 | // reason.message === 'WHOOPS' 741 | }); 742 | ``` 743 | 744 | @method reject 745 | @static 746 | @param {Any} reason value that the returned promise will be rejected with. 747 | Useful for tooling. 748 | @return {Promise} a promise rejected with the given `reason`. 749 | */ 750 | function reject(reason) { 751 | /*jshint validthis:true */ 752 | var Constructor = this; 753 | var promise = new Constructor(noop); 754 | _reject(promise, reason); 755 | return promise; 756 | } 757 | 758 | function needsResolver() { 759 | throw new TypeError('You must pass a resolver function as the first argument to the promise constructor'); 760 | } 761 | 762 | function needsNew() { 763 | throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function."); 764 | } 765 | 766 | /** 767 | Promise objects represent the eventual result of an asynchronous operation. The 768 | primary way of interacting with a promise is through its `then` method, which 769 | registers callbacks to receive either a promise's eventual value or the reason 770 | why the promise cannot be fulfilled. 771 | 772 | Terminology 773 | ----------- 774 | 775 | - `promise` is an object or function with a `then` method whose behavior conforms to this specification. 776 | - `thenable` is an object or function that defines a `then` method. 777 | - `value` is any legal JavaScript value (including undefined, a thenable, or a promise). 778 | - `exception` is a value that is thrown using the throw statement. 779 | - `reason` is a value that indicates why a promise was rejected. 780 | - `settled` the final resting state of a promise, fulfilled or rejected. 781 | 782 | A promise can be in one of three states: pending, fulfilled, or rejected. 783 | 784 | Promises that are fulfilled have a fulfillment value and are in the fulfilled 785 | state. Promises that are rejected have a rejection reason and are in the 786 | rejected state. A fulfillment value is never a thenable. 787 | 788 | Promises can also be said to *resolve* a value. If this value is also a 789 | promise, then the original promise's settled state will match the value's 790 | settled state. So a promise that *resolves* a promise that rejects will 791 | itself reject, and a promise that *resolves* a promise that fulfills will 792 | itself fulfill. 793 | 794 | 795 | Basic Usage: 796 | ------------ 797 | 798 | ```js 799 | let promise = new Promise(function(resolve, reject) { 800 | // on success 801 | resolve(value); 802 | 803 | // on failure 804 | reject(reason); 805 | }); 806 | 807 | promise.then(function(value) { 808 | // on fulfillment 809 | }, function(reason) { 810 | // on rejection 811 | }); 812 | ``` 813 | 814 | Advanced Usage: 815 | --------------- 816 | 817 | Promises shine when abstracting away asynchronous interactions such as 818 | `XMLHttpRequest`s. 819 | 820 | ```js 821 | function getJSON(url) { 822 | return new Promise(function(resolve, reject){ 823 | let xhr = new XMLHttpRequest(); 824 | 825 | xhr.open('GET', url); 826 | xhr.onreadystatechange = handler; 827 | xhr.responseType = 'json'; 828 | xhr.setRequestHeader('Accept', 'application/json'); 829 | xhr.send(); 830 | 831 | function handler() { 832 | if (this.readyState === this.DONE) { 833 | if (this.status === 200) { 834 | resolve(this.response); 835 | } else { 836 | reject(new Error('getJSON: `' + url + '` failed with status: [' + this.status + ']')); 837 | } 838 | } 839 | }; 840 | }); 841 | } 842 | 843 | getJSON('/posts.json').then(function(json) { 844 | // on fulfillment 845 | }, function(reason) { 846 | // on rejection 847 | }); 848 | ``` 849 | 850 | Unlike callbacks, promises are great composable primitives. 851 | 852 | ```js 853 | Promise.all([ 854 | getJSON('/posts'), 855 | getJSON('/comments') 856 | ]).then(function(values){ 857 | values[0] // => postsJSON 858 | values[1] // => commentsJSON 859 | 860 | return values; 861 | }); 862 | ``` 863 | 864 | @class Promise 865 | @param {function} resolver 866 | Useful for tooling. 867 | @constructor 868 | */ 869 | function Promise(resolver) { 870 | this[PROMISE_ID] = nextId(); 871 | this._result = this._state = undefined; 872 | this._subscribers = []; 873 | 874 | if (noop !== resolver) { 875 | typeof resolver !== 'function' && needsResolver(); 876 | this instanceof Promise ? initializePromise(this, resolver) : needsNew(); 877 | } 878 | } 879 | 880 | Promise.all = all; 881 | Promise.race = race; 882 | Promise.resolve = resolve; 883 | Promise.reject = reject; 884 | Promise._setScheduler = setScheduler; 885 | Promise._setAsap = setAsap; 886 | Promise._asap = asap; 887 | 888 | Promise.prototype = { 889 | constructor: Promise, 890 | 891 | /** 892 | The primary way of interacting with a promise is through its `then` method, 893 | which registers callbacks to receive either a promise's eventual value or the 894 | reason why the promise cannot be fulfilled. 895 | 896 | ```js 897 | findUser().then(function(user){ 898 | // user is available 899 | }, function(reason){ 900 | // user is unavailable, and you are given the reason why 901 | }); 902 | ``` 903 | 904 | Chaining 905 | -------- 906 | 907 | The return value of `then` is itself a promise. This second, 'downstream' 908 | promise is resolved with the return value of the first promise's fulfillment 909 | or rejection handler, or rejected if the handler throws an exception. 910 | 911 | ```js 912 | findUser().then(function (user) { 913 | return user.name; 914 | }, function (reason) { 915 | return 'default name'; 916 | }).then(function (userName) { 917 | // If `findUser` fulfilled, `userName` will be the user's name, otherwise it 918 | // will be `'default name'` 919 | }); 920 | 921 | findUser().then(function (user) { 922 | throw new Error('Found user, but still unhappy'); 923 | }, function (reason) { 924 | throw new Error('`findUser` rejected and we're unhappy'); 925 | }).then(function (value) { 926 | // never reached 927 | }, function (reason) { 928 | // if `findUser` fulfilled, `reason` will be 'Found user, but still unhappy'. 929 | // If `findUser` rejected, `reason` will be '`findUser` rejected and we're unhappy'. 930 | }); 931 | ``` 932 | If the downstream promise does not specify a rejection handler, rejection reasons will be propagated further downstream. 933 | 934 | ```js 935 | findUser().then(function (user) { 936 | throw new PedagogicalException('Upstream error'); 937 | }).then(function (value) { 938 | // never reached 939 | }).then(function (value) { 940 | // never reached 941 | }, function (reason) { 942 | // The `PedgagocialException` is propagated all the way down to here 943 | }); 944 | ``` 945 | 946 | Assimilation 947 | ------------ 948 | 949 | Sometimes the value you want to propagate to a downstream promise can only be 950 | retrieved asynchronously. This can be achieved by returning a promise in the 951 | fulfillment or rejection handler. The downstream promise will then be pending 952 | until the returned promise is settled. This is called *assimilation*. 953 | 954 | ```js 955 | findUser().then(function (user) { 956 | return findCommentsByAuthor(user); 957 | }).then(function (comments) { 958 | // The user's comments are now available 959 | }); 960 | ``` 961 | 962 | If the assimliated promise rejects, then the downstream promise will also reject. 963 | 964 | ```js 965 | findUser().then(function (user) { 966 | return findCommentsByAuthor(user); 967 | }).then(function (comments) { 968 | // If `findCommentsByAuthor` fulfills, we'll have the value here 969 | }, function (reason) { 970 | // If `findCommentsByAuthor` rejects, we'll have the reason here 971 | }); 972 | ``` 973 | 974 | Simple Example 975 | -------------- 976 | 977 | Synchronous Example 978 | 979 | ```javascript 980 | let result; 981 | 982 | try { 983 | result = findResult(); 984 | // success 985 | } catch(reason) { 986 | // failure 987 | } 988 | ``` 989 | 990 | Errback Example 991 | 992 | ```js 993 | findResult(function(result, err){ 994 | if (err) { 995 | // failure 996 | } else { 997 | // success 998 | } 999 | }); 1000 | ``` 1001 | 1002 | Promise Example; 1003 | 1004 | ```javascript 1005 | findResult().then(function(result){ 1006 | // success 1007 | }, function(reason){ 1008 | // failure 1009 | }); 1010 | ``` 1011 | 1012 | Advanced Example 1013 | -------------- 1014 | 1015 | Synchronous Example 1016 | 1017 | ```javascript 1018 | let author, books; 1019 | 1020 | try { 1021 | author = findAuthor(); 1022 | books = findBooksByAuthor(author); 1023 | // success 1024 | } catch(reason) { 1025 | // failure 1026 | } 1027 | ``` 1028 | 1029 | Errback Example 1030 | 1031 | ```js 1032 | 1033 | function foundBooks(books) { 1034 | 1035 | } 1036 | 1037 | function failure(reason) { 1038 | 1039 | } 1040 | 1041 | findAuthor(function(author, err){ 1042 | if (err) { 1043 | failure(err); 1044 | // failure 1045 | } else { 1046 | try { 1047 | findBoooksByAuthor(author, function(books, err) { 1048 | if (err) { 1049 | failure(err); 1050 | } else { 1051 | try { 1052 | foundBooks(books); 1053 | } catch(reason) { 1054 | failure(reason); 1055 | } 1056 | } 1057 | }); 1058 | } catch(error) { 1059 | failure(err); 1060 | } 1061 | // success 1062 | } 1063 | }); 1064 | ``` 1065 | 1066 | Promise Example; 1067 | 1068 | ```javascript 1069 | findAuthor(). 1070 | then(findBooksByAuthor). 1071 | then(function(books){ 1072 | // found books 1073 | }).catch(function(reason){ 1074 | // something went wrong 1075 | }); 1076 | ``` 1077 | 1078 | @method then 1079 | @param {Function} onFulfilled 1080 | @param {Function} onRejected 1081 | Useful for tooling. 1082 | @return {Promise} 1083 | */ 1084 | then: then, 1085 | 1086 | /** 1087 | `catch` is simply sugar for `then(undefined, onRejection)` which makes it the same 1088 | as the catch block of a try/catch statement. 1089 | 1090 | ```js 1091 | function findAuthor(){ 1092 | throw new Error('couldn't find that author'); 1093 | } 1094 | 1095 | // synchronous 1096 | try { 1097 | findAuthor(); 1098 | } catch(reason) { 1099 | // something went wrong 1100 | } 1101 | 1102 | // async with promises 1103 | findAuthor().catch(function(reason){ 1104 | // something went wrong 1105 | }); 1106 | ``` 1107 | 1108 | @method catch 1109 | @param {Function} onRejection 1110 | Useful for tooling. 1111 | @return {Promise} 1112 | */ 1113 | 'catch': function _catch(onRejection) { 1114 | return this.then(null, onRejection); 1115 | } 1116 | }; 1117 | 1118 | function polyfill() { 1119 | var local = undefined; 1120 | 1121 | if (typeof global !== 'undefined') { 1122 | local = global; 1123 | } else if (typeof self !== 'undefined') { 1124 | local = self; 1125 | } else { 1126 | try { 1127 | local = Function('return this')(); 1128 | } catch (e) { 1129 | throw new Error('polyfill failed because global object is unavailable in this environment'); 1130 | } 1131 | } 1132 | 1133 | var P = local.Promise; 1134 | 1135 | if (P) { 1136 | var promiseToString = null; 1137 | try { 1138 | promiseToString = Object.prototype.toString.call(P.resolve()); 1139 | } catch (e) { 1140 | // silently ignored 1141 | } 1142 | 1143 | if (promiseToString === '[object Promise]' && !P.cast) { 1144 | return; 1145 | } 1146 | } 1147 | 1148 | local.Promise = Promise; 1149 | } 1150 | 1151 | // Strange compat.. 1152 | Promise.polyfill = polyfill; 1153 | Promise.Promise = Promise; 1154 | 1155 | return Promise; 1156 | 1157 | }))); 1158 | //# sourceMappingURL=es6-promise.map 1159 | --------------------------------------------------------------------------------