├── .gitignore ├── README.md ├── dist ├── salt-fetch.js └── salt-fetch.min.js ├── gulpfile.js ├── index.html ├── package.json └── src └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .deploy/ 3 | design/ 4 | .idea/ 5 | docs/ 6 | test-dist/ 7 | ignore/ 8 | 9 | # Packages # 10 | ############ 11 | # it's better to unpack these files and commit the raw source 12 | # git has its own built in compression methods 13 | *.7z 14 | *.dmg 15 | *.gz 16 | *.iso 17 | *.jar 18 | *.rar 19 | *.tar 20 | *.zip 21 | 22 | # Logs and databases # 23 | ###################### 24 | *.log 25 | *.sql 26 | *.sqlite 27 | 28 | # OS generated files # 29 | ###################### 30 | .DS_Store 31 | .DS_Store? 32 | ._* 33 | .Spotlight-V100 34 | .Trashes 35 | ehthumbs.db 36 | Thumbs.db 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # salt.fetch 2 | 3 | 独立的`ajax/jsonp`模块。 4 | 5 | ## 特别说明 6 | 7 | `salt.fetch`就是 [nattyFetch](https://github.com/Jias/natty-fetch) 和 [nattyStorage](https://github.com/Jias/natty-storage) 的引用合集,看[源代码](https://github.com/saltjs/salt-fetch/blob/master/src/index.js)便知。 8 | 9 | 为什么这么做?为了给`salt`的使用者提供一致的开发体验,故将`nattyFetch`工具以`fetch`属性的方式集成在`salt`命名空间下。 10 | 11 | ## 安装 12 | 13 | 通过`npm`下载代码,目前最新版本为`2.0.0` 14 | 15 | ```shell 16 | npm install salt-fetch --save 17 | ``` 18 | 19 | 插入代码 20 | 21 | ```html 22 | 23 | ``` 24 | 25 | ## 文档 26 | 27 | `salt.fetch`的特点和文档,直接见 [nattyFetch](https://github.com/Jias/natty-fetch) 的文档即可。原文档中使用`nattyFetch`的地方,都可以直接使用`salt.fetch`替换,一模一样,如: 28 | 29 | 原`nattyFetch`文档: 30 | 31 | ```js 32 | const context = nattyFetch.context({ 33 | urlPrefix: '//example.com/api/' 34 | }); 35 | context.create({ 36 | getList: { 37 | url: 'getList.do', 38 | plugins: [ 39 | nattyFetch.plugin.soon 40 | ] 41 | } 42 | }); 43 | module.exports = context.api; 44 | ``` 45 | 46 | 使用`salt.fetch`后: 47 | 48 | ```js 49 | const context = salt.fetch.context({ // 用`salt.fetch`替换`nattyFetch` 50 | urlPrefix: '//example.com/api/' 51 | }); 52 | context.create({ 53 | getList: { 54 | url: 'getList.do', 55 | plugins: [ 56 | salt.fetch.plugin.soon // 用`salt.fetch`替换`nattyFetch` 57 | ] 58 | } 59 | }); 60 | module.exports = context.api; 61 | ``` 62 | 63 | > `salt.fetch`和`nattyFetch`的不同之处: 64 | > 65 | > * `salt.fetch`只有移动端版本。 66 | > * `salt-fetch.js`文件内置了`natty-fetch.js`和`natty-storage.js`两个文件的内容。而`natty-fetch.js`没有内置`natty-storage.js`文件的内容。 67 | -------------------------------------------------------------------------------- /dist/salt-fetch.js: -------------------------------------------------------------------------------- 1 | (function webpackUniversalModuleDefinition(root, factory) { 2 | if(typeof exports === 'object' && typeof module === 'object') 3 | module.exports = factory(); 4 | else if(typeof define === 'function' && define.amd) 5 | define("saltFetch", [], factory); 6 | else if(typeof exports === 'object') 7 | exports["saltFetch"] = factory(); 8 | else 9 | root["saltFetch"] = factory(); 10 | })(this, function() { 11 | return /******/ (function(modules) { // webpackBootstrap 12 | /******/ // The module cache 13 | /******/ var installedModules = {}; 14 | 15 | /******/ // The require function 16 | /******/ function __webpack_require__(moduleId) { 17 | 18 | /******/ // Check if module is in cache 19 | /******/ if(installedModules[moduleId]) 20 | /******/ return installedModules[moduleId].exports; 21 | 22 | /******/ // Create a new module (and put it into the cache) 23 | /******/ var module = installedModules[moduleId] = { 24 | /******/ exports: {}, 25 | /******/ id: moduleId, 26 | /******/ loaded: false 27 | /******/ }; 28 | 29 | /******/ // Execute the module function 30 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 31 | 32 | /******/ // Flag the module as loaded 33 | /******/ module.loaded = true; 34 | 35 | /******/ // Return the exports of the module 36 | /******/ return module.exports; 37 | /******/ } 38 | 39 | 40 | /******/ // expose the modules object (__webpack_modules__) 41 | /******/ __webpack_require__.m = modules; 42 | 43 | /******/ // expose the module cache 44 | /******/ __webpack_require__.c = installedModules; 45 | 46 | /******/ // __webpack_public_path__ 47 | /******/ __webpack_require__.p = ""; 48 | 49 | /******/ // Load entry module and return exports 50 | /******/ return __webpack_require__(0); 51 | /******/ }) 52 | /************************************************************************/ 53 | /******/ ([ 54 | /* 0 */ 55 | /***/ function(module, exports, __webpack_require__) { 56 | 57 | var salt = window.salt = window.salt || {}; 58 | var nattyFetch = __webpack_require__(1); 59 | var nattyStorage = __webpack_require__(2); 60 | salt.fetch = nattyFetch; 61 | salt.storage = nattyStorage; 62 | 63 | module.exports = { 64 | fetch: nattyFetch, 65 | storage: nattyStorage 66 | }; 67 | 68 | /***/ }, 69 | /* 1 */ 70 | /***/ function(module, exports, __webpack_require__) { 71 | 72 | (function webpackUniversalModuleDefinition(root, factory) { 73 | if(true) 74 | module.exports = factory(__webpack_require__(2)); 75 | else if(typeof define === 'function' && define.amd) 76 | define("nattyFetch", ["natty-storage"], factory); 77 | else if(typeof exports === 'object') 78 | exports["nattyFetch"] = factory(require("natty-storage")); 79 | else 80 | root["nattyFetch"] = factory(root["nattyStorage"]); 81 | })(this, function(__WEBPACK_EXTERNAL_MODULE_2__) { 82 | return /******/ (function(modules) { // webpackBootstrap 83 | /******/ // The module cache 84 | /******/ var installedModules = {}; 85 | /******/ 86 | /******/ // The require function 87 | /******/ function __webpack_require__(moduleId) { 88 | /******/ 89 | /******/ // Check if module is in cache 90 | /******/ if(installedModules[moduleId]) 91 | /******/ return installedModules[moduleId].exports; 92 | /******/ 93 | /******/ // Create a new module (and put it into the cache) 94 | /******/ var module = installedModules[moduleId] = { 95 | /******/ exports: {}, 96 | /******/ id: moduleId, 97 | /******/ loaded: false 98 | /******/ }; 99 | /******/ 100 | /******/ // Execute the module function 101 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 102 | /******/ 103 | /******/ // Flag the module as loaded 104 | /******/ module.loaded = true; 105 | /******/ 106 | /******/ // Return the exports of the module 107 | /******/ return module.exports; 108 | /******/ } 109 | /******/ 110 | /******/ 111 | /******/ // expose the modules object (__webpack_modules__) 112 | /******/ __webpack_require__.m = modules; 113 | /******/ 114 | /******/ // expose the module cache 115 | /******/ __webpack_require__.c = installedModules; 116 | /******/ 117 | /******/ // __webpack_public_path__ 118 | /******/ __webpack_require__.p = ""; 119 | /******/ 120 | /******/ // Load entry module and return exports 121 | /******/ return __webpack_require__(0); 122 | /******/ }) 123 | /************************************************************************/ 124 | /******/ ([ 125 | /* 0 */ 126 | /***/ function(module, exports, __webpack_require__) { 127 | 128 | 'use strict'; 129 | 130 | module.exports = __webpack_require__(1); 131 | 132 | /***/ }, 133 | /* 1 */ 134 | /***/ function(module, exports, __webpack_require__) { 135 | 136 | "use strict"; 137 | 138 | var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 139 | 140 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 141 | 142 | var nattyStorage = __webpack_require__(2); 143 | 144 | if (nattyStorage === undefined) { 145 | console.warn('Please install the `natty-storage` script which is required by `natty-fetch`, go on with' + ' https://www.npmjs.com/package/natty-storage'); 146 | } 147 | 148 | // 下面两个配置了webpack的alias 149 | var ajax = __webpack_require__(3); 150 | var jsonp = __webpack_require__(5); 151 | 152 | var Defer = __webpack_require__(6); 153 | var util = __webpack_require__(4); 154 | var event = __webpack_require__(7); 155 | 156 | // 内置插件 157 | var pluginLoop = __webpack_require__(8); 158 | var pluginSoon = __webpack_require__(9); 159 | 160 | var extend = util.extend; 161 | var runAsFn = util.runAsFn; 162 | var isAbsoluteUrl = util.isAbsoluteUrl; 163 | var isRelativeUrl = util.isRelativeUrl; 164 | var noop = util.noop; 165 | var isBoolean = util.isBoolean; 166 | var isArray = util.isArray; 167 | var isFunction = util.isFunction; 168 | var sortPlainObjectKey = util.sortPlainObjectKey; 169 | var isEmptyObject = util.isEmptyObject; 170 | var isPlainObject = util.isPlainObject; 171 | var dummyPromise = util.dummyPromise; 172 | var isString = util.isString; 173 | 174 | var NULL = null; 175 | var EMPTY = ''; 176 | var TRUE = true; 177 | var FALSE = !TRUE; 178 | 179 | // 全局默认配置 180 | var defaultGlobalConfig = { 181 | 182 | // 默认参数 183 | data: {}, 184 | 185 | // 请求完成钩子函数 186 | didFetch: noop, 187 | 188 | // 预处理回调 189 | fit: noop, 190 | 191 | // 自定义header, 只针对非跨域的ajax有效, 跨域时将忽略自定义header 192 | header: {}, 193 | 194 | // 是否忽律接口自身的并发请求 195 | ignoreSelfConcurrent: FALSE, 196 | 197 | // 有两种格式配置`jsonp`的值 198 | // {Boolean} 199 | // {Array} eg: [TRUE, 'cb', 'j{id}'] 200 | jsonp: FALSE, 201 | 202 | // 是否开启log信息 203 | log: FALSE, 204 | 205 | // 非GET方式对JSONP无效 206 | method: 'GET', 207 | 208 | // 是否开启mock模式 209 | mock: FALSE, 210 | 211 | mockUrl: EMPTY, 212 | 213 | // 全局`mockUrl`前缀 214 | mockUrlPrefix: EMPTY, 215 | 216 | // 成功回调 217 | process: noop, 218 | 219 | // 默认不执行重试 220 | retry: 0, 221 | 222 | // 使用已有的request方法 223 | request: NULL, 224 | 225 | // 0表示不启动超时处理 226 | timeout: 0, 227 | 228 | // http://zeptojs.com/#$.param 229 | traditional: FALSE, 230 | 231 | url: EMPTY, 232 | 233 | // 全局`url`前缀 234 | urlPrefix: EMPTY, 235 | 236 | // 是否在`url`上添加时间戳, 用于避免浏览器的304缓存 237 | urlStamp: TRUE, 238 | 239 | // TODO 文档中没有暴露 240 | withCredentials: NULL, 241 | 242 | // 请求之前调用的钩子函数 243 | willFetch: noop, 244 | 245 | // 扩展: storage 246 | storage: false, 247 | 248 | // 插件 249 | // 目前只支持两种插件 250 | // plugins: [ 251 | // nattyFetch.plugin.loop 252 | // nattyFetch.plugin.soon 253 | // ] 254 | plugins: false 255 | }; 256 | 257 | var runtimeGlobalConfig = extend({}, defaultGlobalConfig); 258 | 259 | var API = (function () { 260 | function API(path, options, contextConfig, contextId) { 261 | _classCallCheck(this, API); 262 | 263 | var t = this; 264 | t.contextConfig = contextConfig; 265 | t._path = path; 266 | 267 | var config = t.config = t.processAPIOptions(options); 268 | 269 | /** 270 | * 一个`DB`的`api`的实现 271 | * @param data {Object|Function} 272 | * @returns {Object} Promise Object 273 | */ 274 | t.api = function (data) { 275 | data = data || {}; 276 | // 是否忽略自身的并发请求 277 | if (config.ignoreSelfConcurrent && t.api.pending) { 278 | return dummyPromise; 279 | } 280 | 281 | if (config.overrideSelfConcurrent && config._lastRequester) { 282 | config._lastRequester.abort(); 283 | delete config._lastRequester; 284 | } 285 | 286 | var vars = t.makeVars(data); 287 | 288 | if (config.retry === 0) { 289 | return t.request(vars, config); 290 | } else { 291 | return t.tryRequest(vars, config); 292 | } 293 | }; 294 | 295 | t.api.contextId = contextId; 296 | t.api._path = path; 297 | 298 | // 标记是否正在等待请求返回 299 | t.api.pending = FALSE; 300 | 301 | t.api.config = config; 302 | 303 | t.initStorage(); 304 | 305 | // 启动插件 306 | var plugins = isArray(options.plugins) ? options.plugins : []; 307 | for (var i = 0, l = plugins.length; i < l; i++) { 308 | plugins[i].call(t, t.api); 309 | } 310 | } 311 | 312 | /** 313 | * 关键词 314 | * 语意化的 315 | * 优雅的 316 | * 功能增强的 317 | * 底层隔离的 318 | */ 319 | 320 | _createClass(API, [{ 321 | key: 'makeVars', 322 | value: function makeVars(data) { 323 | var t = this; 324 | var config = t.config; 325 | 326 | // 一次请求的私有相关数据 327 | var vars = { 328 | mark: { 329 | __api: t._path 330 | } 331 | }; 332 | 333 | if (config.mock) { 334 | vars.mark.__mock = TRUE; 335 | } 336 | 337 | if (config.urlStamp) { 338 | vars.mark.__stamp = +new Date(); 339 | } 340 | 341 | // `data`必须在请求发生时实时创建 342 | data = extend({}, config.data, runAsFn(data)); 343 | 344 | // 将数据参数存在私有标记中, 方便API的`process`方法内部使用 345 | vars.data = data; 346 | 347 | return vars; 348 | } 349 | 350 | /** 351 | * 处理API的配置 352 | * @param options {Object} 353 | */ 354 | }, { 355 | key: 'processAPIOptions', 356 | value: function processAPIOptions(options) { 357 | 358 | var t = this; 359 | var config = extend({}, t.contextConfig, options); 360 | 361 | if (config.mock) { 362 | config.mockUrl = t.getFullUrl(config); 363 | } 364 | 365 | config.url = t.getFullUrl(config); 366 | 367 | // 按照[boolean, callbackKeyWord, callbackFunctionName]格式处理 368 | if (isArray(options.jsonp)) { 369 | config.jsonp = isBoolean(options.jsonp[0]) ? options.jsonp[0] : FALSE; 370 | // 这个参数只用于jsonp 371 | if (config.jsonp) { 372 | config.jsonpFlag = options.jsonp[1]; 373 | config.jsonpCallbackName = options.jsonp[2]; 374 | } 375 | } 376 | 377 | // 配置自动增强 如果`url`的值有`.jsonp`结尾 则认为是`jsonp`请求 378 | // NOTE jsonp是描述正式接口的 不影响mock接口!!! 379 | if (!config.mock && !!config.url.match(/\.jsonp(\?.*)?$/)) { 380 | config.jsonp = TRUE; 381 | } 382 | 383 | return config; 384 | } 385 | }, { 386 | key: 'initStorage', 387 | value: function initStorage() { 388 | var t = this; 389 | var config = t.config; 390 | 391 | // 开启`storage`的前提条件 392 | var storagePrecondition = config.method === 'GET' || config.jsonp; 393 | 394 | // 不满足`storage`使用条件的情况下, 开启`storage`将抛出错误 395 | if (!storagePrecondition && config.storage === TRUE) { 396 | throw new Error('A `' + config.method + '` request CAN NOT use `storage` which is only for `GET/jsonp`' + ' request! Please check the options for `' + t._path + '`'); 397 | } 398 | 399 | // 简易开启缓存的写法 400 | if (config.storage === TRUE) { 401 | config.storage = {}; 402 | } 403 | 404 | // 决定什么情况下缓存可以开启 405 | t.api.storageUseable = isPlainObject(config.storage) && (config.method === 'GET' || config.jsonp) && nattyStorage.support[config.storage.type || 'localStorage']; 406 | 407 | // 创建缓存实例 408 | if (t.api.storageUseable) { 409 | // `key`和`id`的选择原则: 410 | // `key`只选用相对稳定的值, 减少因为`key`的改变而增加的残留缓存 411 | // 经常变化的值用于`id`, 如一个接口在开发过程中可能使用方式不一样, 会在`jsonp`和`get`之间切换。 412 | t.api.storage = nattyStorage(extend({ 413 | key: [t.api.contextId, t._path].join('_') 414 | }, config.storage, { 415 | async: TRUE, 416 | id: [config.storage.id, config.jsonp ? 'jsonp' : config.method, config.url].join('_') // 使用者的`id`和内部的`id`, 要同时生效 417 | })); 418 | } 419 | } 420 | 421 | /** 422 | * 请求数据(从storage或者从网络) 423 | * @param vars {Object} 发送的数据 424 | * @param config {Object} 已经处理完善的请求配置 425 | * @returns {Object} defer对象 426 | */ 427 | }, { 428 | key: 'request', 429 | value: function request(vars, config) { 430 | var t = this; 431 | 432 | return new Promise(function (resolve, reject) { 433 | if (t.api.storageUseable) { 434 | 435 | // 只有GET和JSONP才会有storage生效 436 | vars.queryString = isEmptyObject(vars.data) ? 'no-query-string' : JSON.stringify(sortPlainObjectKey(vars.data)); 437 | 438 | t.api.storage.has(vars.queryString).then(function (data) { 439 | // console.warn('has cached: ', hasValue); 440 | if (data.has) { 441 | // 调用 willFetch 钩子 442 | config.willFetch(vars, config, 'storage'); 443 | return data.value; 444 | } else { 445 | return t.remoteRequest(vars, config); 446 | } 447 | }).then(function (data) { 448 | resolve(data); 449 | })['catch'](function (e) { 450 | reject(e); 451 | }); 452 | } else { 453 | t.remoteRequest(vars, config).then(function (data) { 454 | resolve(data); 455 | })['catch'](function (e) { 456 | reject(e); 457 | }); 458 | } 459 | }); 460 | } 461 | 462 | /** 463 | * 获取正式接口的完整`url` 464 | * @param config {Object} 465 | */ 466 | }, { 467 | key: 'getFullUrl', 468 | value: function getFullUrl(config) { 469 | var url = config.mock ? config.mockUrl : config.url; 470 | if (!url) return EMPTY; 471 | var prefixKey = config.mock ? 'mockUrlPrefix' : 'urlPrefix'; 472 | return config[prefixKey] && !isAbsoluteUrl(url) && !isRelativeUrl(url) ? config[prefixKey] + url : url; 473 | } 474 | 475 | /** 476 | * 发起网络请求 477 | * @param vars 478 | * @param config 479 | * @returns {Promise} 480 | */ 481 | }, { 482 | key: 'remoteRequest', 483 | value: function remoteRequest(vars, config) { 484 | var t = this; 485 | 486 | // 调用 willFetch 钩子 487 | config.willFetch(vars, config, 'remote'); 488 | 489 | // 等待状态在此处开启 在相应的`requester`的`complete`回调中关闭 490 | t.api.pending = TRUE; 491 | 492 | var defer = new Defer(); 493 | 494 | // 创建请求实例requester 495 | if (config.request) { 496 | // 使用已有的request方法 497 | vars.requester = config.request(vars, config, defer); 498 | } else if (config.jsonp) { 499 | vars.requester = t.sendJSONP(vars, config, defer); 500 | } else { 501 | vars.requester = t.sendAjax(vars, config, defer); 502 | } 503 | 504 | // 如果只响应最新请求 505 | if (config.overrideSelfConcurrent) { 506 | config._lastRequester = vars.requester; 507 | } 508 | 509 | // 超时处理 510 | if (0 !== config.timeout) { 511 | setTimeout(function () { 512 | if (t.api.pending && vars.requester) { 513 | // 取消请求 514 | vars.requester.abort(); 515 | delete vars.requester; 516 | var error = { 517 | timeout: TRUE, 518 | message: 'Timeout By ' + config.timeout + 'ms.' 519 | }; 520 | defer.reject(error); 521 | event.fire('g.reject', [error, config]); 522 | event.fire(t.api.contextId + '.reject', [error, config]); 523 | 524 | // 调用 didFetch 钩子 525 | config.didFetch(vars, config); 526 | } 527 | }, config.timeout); 528 | } 529 | return defer.promise; 530 | } 531 | 532 | /** 533 | * 重试功能的实现 534 | * @param vars {Object} 发送的数据 535 | * @param config 536 | * @returns {Object} defer对象 537 | */ 538 | }, { 539 | key: 'tryRequest', 540 | value: function tryRequest(vars, config) { 541 | var t = this; 542 | 543 | return new Promise(function (resolve, reject) { 544 | var retryTime = 0; 545 | var request = function request() { 546 | // 更新的重试次数 547 | vars.mark.__retryTime = retryTime; 548 | t.request(vars, config).then(function (content) { 549 | resolve(content); 550 | event.fire('g.resolve', [content, config], config); 551 | event.fire(t.api.contextId + '.resolve', [content, config], config); 552 | }, function (error) { 553 | if (retryTime === config.retry) { 554 | reject(error); 555 | } else { 556 | retryTime++; 557 | request(); 558 | } 559 | }); 560 | }; 561 | 562 | request(); 563 | }); 564 | } 565 | 566 | /** 567 | * 处理结构化的响应数据 568 | * @param config 569 | * @param response 570 | * @param defer 571 | */ 572 | }, { 573 | key: 'processResponse', 574 | value: function processResponse(vars, config, defer, response) { 575 | var t = this; 576 | 577 | // 调用 didFetch 钩子函数 578 | config.didFetch(vars, config); 579 | 580 | // 非标准格式数据的预处理 581 | response = config.fit(response, vars); 582 | 583 | if (response.success) { 584 | (function () { 585 | // 数据处理 586 | var content = config.process(response.content, vars); 587 | 588 | var resolveDefer = function resolveDefer() { 589 | defer.resolve(content); 590 | event.fire('g.resolve', [content, config], config); 591 | event.fire(t.api.contextId + '.resolve', [content, config], config); 592 | }; 593 | 594 | if (t.api.storageUseable) { 595 | t.api.storage.set(vars.queryString, content).then(function () { 596 | resolveDefer(); 597 | })['catch'](function (e) { 598 | resolveDefer(); 599 | }); 600 | } else { 601 | resolveDefer(); 602 | } 603 | })(); 604 | } else { 605 | var error = extend({ 606 | message: 'Processing Failed: ' + t._path 607 | }, response.error); 608 | // NOTE response是只读的对象!!! 609 | defer.reject(error); 610 | event.fire('g.reject', [error, config]); 611 | event.fire(t.api.contextId + '.reject', [error, config]); 612 | } 613 | } 614 | 615 | /** 616 | * 发起Ajax请求 617 | * @param config {Object} 请求配置 618 | * @param defer {Object} defer对象 619 | * @param retryTime {undefined|Number} 如果没有重试 将是undefined值 见`createAPI`方法 620 | * 如果有重试 将是重试的当前次数 见`tryRequest`方法 621 | * @returns {Object} xhr对象实例 622 | */ 623 | }, { 624 | key: 'sendAjax', 625 | value: function sendAjax(vars, config, defer) { 626 | var t = this; 627 | 628 | return ajax({ 629 | traditional: config.traditional, 630 | cache: config.cache, 631 | mark: vars.mark, 632 | log: config.log, 633 | url: config.mock ? config.mockUrl : config.url, 634 | method: config.method, 635 | data: vars.data, 636 | header: config.header, 637 | withCredentials: config.withCredentials, 638 | // 强制约定json 639 | accept: 'json', 640 | success: function success(response /*, xhr*/) { 641 | t.processResponse(vars, config, defer, response); 642 | }, 643 | error: function error(status /*, xhr*/) { 644 | 645 | var message = undefined; 646 | var flag = undefined; 647 | switch (status) { 648 | case 404: 649 | message = 'Not Found'; 650 | break; 651 | case 500: 652 | message = 'Internal Server Error'; 653 | break; 654 | // TODO 是否要补充其他明确的服务端错误 655 | default: 656 | message = 'Unknown Server Error'; 657 | break; 658 | } 659 | 660 | defer.reject({ 661 | status: status, 662 | message: message 663 | }); 664 | }, 665 | complete: function complete() /*status, xhr*/{ 666 | if (vars.retryTime === undefined || vars.retryTime === config.retry) { 667 | //C.log('ajax complete'); 668 | 669 | t.api.pending = FALSE; 670 | vars.requester = NULL; 671 | 672 | // 如果只响应最新请求 673 | if (config.overrideSelfConcurrent) { 674 | delete config._lastRequester; 675 | } 676 | } 677 | //console.log('__complete: pending:', config.pending, 'retryTime:', retryTime, Math.random()); 678 | } 679 | }); 680 | } 681 | 682 | /** 683 | * 发起jsonp请求 684 | * @param vars {Object} 一次请求相关的私有数据 685 | * @param config {Object} 请求配置 686 | * @param defer {Object} defer对象 687 | * @param retryTime {undefined|Number} 如果没有重试 将是undefined值 见`createAPI`方法 688 | * 如果有重试 将是重试的当前次数 见`tryRequest`方法 689 | * @returns {Object} 带有abort方法的对象 690 | */ 691 | }, { 692 | key: 'sendJSONP', 693 | value: function sendJSONP(vars, config, defer) { 694 | var t = this; 695 | return jsonp({ 696 | traditional: config.traditional, 697 | log: config.log, 698 | mark: vars.mark, 699 | url: config.mock ? config.mockUrl : config.url, 700 | data: vars.data, 701 | cache: config.cache, 702 | flag: config.jsonpFlag, 703 | callbackName: config.jsonpCallbackName, 704 | success: function success(response) { 705 | t.processResponse(vars, config, defer, response); 706 | }, 707 | error: function error(e) { 708 | defer.reject({ 709 | message: 'Not Accessable JSONP `' 710 | // TODO show url 711 | }); 712 | }, 713 | complete: function complete() { 714 | if (vars.retryTime === undefined || vars.retryTime === config.retry) { 715 | t.api.pending = FALSE; 716 | vars.requester = NULL; 717 | 718 | // 如果只响应最新请求 719 | if (config.overrideSelfConcurrent) { 720 | delete config._lastRequester; 721 | } 722 | } 723 | } 724 | }); 725 | } 726 | }]); 727 | 728 | return API; 729 | })(); 730 | 731 | var context = (function () { 732 | var count = 0; 733 | 734 | return function (contextId, options) { 735 | 736 | if (isString(contextId)) { 737 | options = options || {}; 738 | } else { 739 | options = contextId || {}; 740 | contextId = 'c' + count++; 741 | } 742 | 743 | var storage = nattyStorage({ 744 | type: 'variable', 745 | key: contextId 746 | }); 747 | 748 | var ctx = {}; 749 | 750 | ctx.api = storage.get(); 751 | 752 | ctx._contextId = contextId; 753 | 754 | ctx._config = extend({}, runtimeGlobalConfig, options); 755 | 756 | /** 757 | * 创建api 758 | * @param namespace {String} optional 759 | * @param APIs {Object} 该`namespace`下的`api`配置 760 | */ 761 | ctx.create = function (namespace, APIs) { 762 | var hasNamespace = arguments.length === 2 && isString(namespace); 763 | 764 | if (!hasNamespace) { 765 | APIs = namespace; 766 | } 767 | 768 | for (var path in APIs) { 769 | storage.set(hasNamespace ? namespace + '.' + path : path, new API(hasNamespace ? namespace + '.' + path : path, runAsFn(APIs[path]), ctx._config, contextId).api); 770 | } 771 | 772 | ctx.api = storage.get(); 773 | }; 774 | 775 | // 绑定上下文事件 776 | ctx.on = function (name, fn) { 777 | if (!isFunction(fn)) return; 778 | event.on(ctx._contextId + '.' + name, fn); 779 | return ctx; 780 | }; 781 | 782 | return ctx; 783 | }; 784 | })(); 785 | 786 | var VERSION = undefined; 787 | (VERSION = "2.0.0"); 788 | 789 | var ONLY_FOR_MODERN_BROWSER = undefined; 790 | (ONLY_FOR_MODERN_BROWSER = true); 791 | 792 | /** 793 | * 简易接口 794 | * @param options 795 | * @note 这个接口尝试做过共享`api`实例, 但是结果证明不现实, 不科学, 不要再尝试了! 796 | * 因为无法共享实例, 所以有些功能是不支持的: 797 | * - ignoreSelfConcurrent 798 | * - overrideSelfConcurrent 799 | * - 所有缓存相关的功能 800 | */ 801 | var nattyFetch = function nattyFetch(options) { 802 | return new API('nattyFetch', runAsFn(options), defaultGlobalConfig, 'global').api(); 803 | }; 804 | 805 | extend(nattyFetch, { 806 | onlyForModern: ONLY_FOR_MODERN_BROWSER, 807 | version: VERSION, 808 | // Context, 809 | _util: util, 810 | _event: event, 811 | context: context, 812 | ajax: ajax, 813 | jsonp: jsonp, 814 | 815 | /** 816 | * 执行全局配置 817 | * @param options 818 | */ 819 | setGlobal: function setGlobal(options) { 820 | runtimeGlobalConfig = extend({}, defaultGlobalConfig, options); 821 | return this; 822 | }, 823 | 824 | /** 825 | * 获取全局配置 826 | * @param property {String} optional 827 | * @returns {*} 828 | */ 829 | getGlobal: function getGlobal(property) { 830 | return property ? runtimeGlobalConfig[property] : runtimeGlobalConfig; 831 | }, 832 | 833 | // 绑定全局事件 834 | on: function on(name, fn) { 835 | if (!isFunction(fn)) return; 836 | event.on('g.' + name, fn); 837 | return this; 838 | }, 839 | 840 | /** 841 | * 插件名称空间 842 | */ 843 | plugin: { 844 | loop: pluginLoop, 845 | soon: pluginSoon 846 | } 847 | }); 848 | 849 | // 内部直接将运行时的全局配置初始化到默认值 850 | nattyFetch.setGlobal(defaultGlobalConfig); 851 | 852 | module.exports = nattyFetch; 853 | 854 | /***/ }, 855 | /* 2 */ 856 | /***/ function(module, exports) { 857 | 858 | module.exports = __WEBPACK_EXTERNAL_MODULE_2__; 859 | 860 | /***/ }, 861 | /* 3 */ 862 | /***/ function(module, exports, __webpack_require__) { 863 | 864 | /** 865 | * file: ajax.js 866 | * ref https://xhr.spec.whatwg.org 867 | * ref https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest 868 | * ref https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest 869 | * ref https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS 870 | * ref https://hacks.mozilla.org/2009/07/cross-site-xmlhttprequest-with-cors/ 871 | * ref http://www.html5rocks.com/en/tutorials/cors/ 872 | * @link http://enable-cors.org/index.html 873 | */ 874 | 'use strict'; 875 | 876 | var _require = __webpack_require__(4); 877 | 878 | var extend = _require.extend; 879 | var appendQueryString = _require.appendQueryString; 880 | var noop = _require.noop; 881 | var isCrossDomain = _require.isCrossDomain; 882 | var isBoolean = _require.isBoolean; 883 | var param = _require.param; 884 | 885 | var FALSE = false; 886 | var UNDEFINED = 'undefined'; 887 | var NULL = null; 888 | var GET = 'GET'; 889 | var SCRIPT = 'script'; 890 | var XML = 'xml'; 891 | var JS0N = 'json'; // NOTE 不能使用`JSON`,这里用数字零`0`代替了字母`O` 892 | 893 | var supportCORS = UNDEFINED !== typeof XMLHttpRequest && 'withCredentials' in new XMLHttpRequest(); 894 | 895 | // minetype的简写映射 896 | // TODO 考虑是否优化 897 | var acceptToRequestHeader = { 898 | // IIS returns `application/x-javascript` 但应该不需要支持 899 | '*': '*/' + '*', 900 | script: 'text/javascript, application/javascript, application/ecmascript, application/x-ecmascript', 901 | json: 'application/json, text/json', 902 | xml: 'application/xml, text/xml', 903 | html: 'text/html', 904 | text: 'text/plain' 905 | }; 906 | 907 | // 设置请求头 908 | // 没有处理的事情:跨域时使用者传入的多余的Header没有屏蔽 没必要 909 | var setHeaders = function setHeaders(xhr, options) { 910 | var header = { 911 | Accept: acceptToRequestHeader[options.accept] 912 | }; 913 | // 如果没有跨域 则打该标识 业界通用做法 914 | // TODO 如果是跨域的 只有有限的requestHeader是可以使用的 待补充注释 915 | if (!isCrossDomain(options.url)) { 916 | header['X-Requested-With'] = 'XMLHttpRequest'; 917 | } 918 | 919 | extend(header, options.header); 920 | 921 | // 如果是`POST`请求,需要`urlencode` 922 | if (options.method === 'POST' && !options.header['Content-Type']) { 923 | header['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8'; 924 | } 925 | 926 | for (var key in header) { 927 | xhr.setRequestHeader(key, header[key]); 928 | } 929 | }; 930 | 931 | // 绑定事件 932 | // NOTE 还得继续使用readystatechange事件 933 | // 比较遗憾 到现在了依然不能安全的使用load和error等事件 就连PC端的chrome都有下面的问题 934 | // 500: 触发load loadend 不触发error 935 | // 404: 触发load loadend 不触发error 936 | var setEvents = function setEvents(xhr, options) { 937 | 938 | // 再高级的浏览器都有低级错误! 已经不能在相信了! 939 | // MAC OSX Yosemite Safari上的低级错误: 一次`ajax`请求的`loadend`事件完成之后, 940 | // 如果执行`xhr.abort()`, 居然还能触发一遍`abort`和`loadend`事件!!! 941 | // `__finished`标识一次完整的请求是否结束, 如果已结束, 则不再触发任何事件 942 | xhr.__finished = FALSE; 943 | 944 | var readyStateChangeFn = function readyStateChangeFn(e) { 945 | 946 | //console.log('xhr.readyState', xhr.readyState, 'xhr.status', xhr.status, xhr); 947 | if (xhr.readyState === 4) { 948 | // 如果请求被取消(aborted) 则`xhr.status`会是0 所以不会进入`success`回调 949 | if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) { 950 | //let mime = xhr.getResponseHeader('Content-Type'); 951 | var data = xhr.responseText; 952 | switch (options.accept) { 953 | case JS0N: 954 | try { 955 | data = JSON.parse(data); 956 | } catch (e) { 957 | console.warn('The response can NOT be parsed to JSON object.', data); 958 | } 959 | break; 960 | case SCRIPT: 961 | (1, eval)(data); 962 | break; 963 | case XML: 964 | data = xhr.responseXML; 965 | break; 966 | //case HTML: 967 | //case TEXT: 968 | default: 969 | break; 970 | } 971 | options.success(data, xhr); 972 | } else { 973 | // 如果请求被取消(aborted) 则`xhr.status`会是0 程序也会到达这里 需要排除 不应该触发error 974 | !xhr.__aborted && options.error(xhr.status, xhr); 975 | } 976 | } 977 | }; 978 | 979 | // readyState value: 980 | // 0: UNSET 未初始化 981 | // 1: OPENED 982 | // 2: HEADERS_RECEIVED 983 | // 3: LOADING 984 | // 4: DONE 此时触发load事件 985 | xhr.addEventListener("readystatechange", readyStateChangeFn); 986 | 987 | //xhr.addEventListener('error', function () { 988 | // console.log('xhr event: error'); 989 | //}); 990 | 991 | //xhr.addEventListener('load', function () { 992 | // console.log('xhr event: load'); 993 | //}); 994 | 995 | var abortFn = function abortFn() { 996 | if (xhr.__finished) { 997 | return; 998 | } 999 | //options.log && console.info('~abort'); 1000 | options.abort(xhr.status, xhr); 1001 | }; 1002 | 1003 | xhr.addEventListener('abort', abortFn); 1004 | 1005 | var loadedFn = function loadedFn() { 1006 | if (xhr.__finished) { 1007 | return; 1008 | } 1009 | xhr.__finished = true; 1010 | //options.log && console.info('~loadend'); 1011 | options.complete(xhr.status, xhr); 1012 | delete xhr.__aborted; 1013 | }; 1014 | 1015 | xhr.addEventListener('loadend', loadedFn); 1016 | }; 1017 | 1018 | var defaultOptions = { 1019 | url: '', 1020 | mark: {}, 1021 | method: GET, 1022 | accept: '*', 1023 | data: null, 1024 | header: {}, 1025 | withCredentials: NULL, // 根据`url`是否跨域决定默认值. 如果显式配置该值(必须是布尔值), 则个使用配置值 1026 | success: noop, 1027 | error: noop, 1028 | complete: noop, 1029 | abort: noop, 1030 | log: FALSE, 1031 | traditional: FALSE 1032 | }; 1033 | 1034 | var ajax = function ajax(options) { 1035 | 1036 | options = extend({}, defaultOptions, options); 1037 | 1038 | // 如果跨域了, 则禁止发送自定义的`header`信息 1039 | if (isCrossDomain(options.url)) { 1040 | // 重置`header`, 统一浏览器的行为. 1041 | // 如果在跨域时发送了自定义`header`, 则: 1042 | // 标准浏览器会报错: Request header field xxx is not allowed by Access-Control-Allow-Headers in preflight response. 1043 | // IE浏览器不报错 1044 | options.header = {}; 1045 | } 1046 | 1047 | var xhr = new XMLHttpRequest(); 1048 | 1049 | setEvents(xhr, options); 1050 | 1051 | xhr.open(options.method, appendQueryString(options.url, extend({}, options.mark, options.method === GET ? options.data : {}), options.traditional)); 1052 | 1053 | // NOTE 生产环境的Server端, `Access-Control-Allow-Origin`的值一定不要配置成`*`!!! 而且`Access-Control-Allow-Credentials`应该是true!!! 1054 | // NOTE 如果Server端的`responseHeader`配置了`Access-Control-Allow-Origin`的值是通配符`*` 则前端`withCredentials`是不能使用true值的 1055 | // NOTE 如果Client端`withCredentials`使用了true值 则后端`responseHeader`中必须配置`Access-Control-Allow-Credentials`是true 1056 | xhr.withCredentials = isBoolean(options.withCredentials) ? options.withCredentials : isCrossDomain(options.url); 1057 | 1058 | // 设置requestHeader 1059 | setHeaders(xhr, options); 1060 | 1061 | // 文档建议说 send方法如果不发送请求体数据 则null参数在某些浏览器上是必须的 1062 | xhr.send(options.method === GET ? NULL : options.data !== NULL ? param(options.data, options.traditional) : NULL); 1063 | 1064 | var originAbort = xhr.abort; 1065 | 1066 | // 重写`abort`方法 1067 | xhr.abort = function () { 1068 | xhr.__aborted = true; 1069 | // NOTE 直接调用`originAbort()`时 浏览器会报 `Illegal invocation` 错误 1070 | originAbort.call(xhr); 1071 | }; 1072 | 1073 | return xhr; 1074 | }; 1075 | 1076 | // 移动端不需要fallback 1077 | ajax.fallback = false; 1078 | ajax.supportCORS = supportCORS; 1079 | 1080 | module.exports = ajax; 1081 | 1082 | /***/ }, 1083 | /* 4 */ 1084 | /***/ function(module, exports, __webpack_require__) { 1085 | 1086 | 'use strict'; 1087 | 1088 | var hasWindow = 'undefined' !== typeof window; 1089 | var doc = hasWindow ? document : null; 1090 | var escape = encodeURIComponent; 1091 | var NULL = null; 1092 | var toString = Object.prototype.toString; 1093 | var ARRAY_TYPE = '[object Array]'; 1094 | var OBJECT_TYPE = '[object Object]'; 1095 | var TRUE = true; 1096 | var FALSE = !TRUE; 1097 | 1098 | /** 1099 | * 伪造的`promise`对象 1100 | * NOTE 伪造的promise对象要支持链式调用 保证和`new Promise`返回的对象行为一致 1101 | * dummyPromise.then().catch().finally() 1102 | */ 1103 | var dummyPromise = { 1104 | dummy: TRUE 1105 | }; 1106 | dummyPromise.then = dummyPromise['catch'] = function () { 1107 | // NOTE 这里用了剪头函数 不能用`return this` 1108 | return dummyPromise; 1109 | }; 1110 | 1111 | /** 1112 | * 判断是否是IE8~11, 不包含Edge 1113 | * @returns {boolean} 1114 | * @note IE11下 window.ActiveXObject的值很怪异, 所有需要追加 'ActiveXObject' in window 来判断 1115 | */ 1116 | var isIE = hasWindow && (!!window.ActiveXObject || 'ActiveXObject' in window); 1117 | 1118 | var noop = function noop(v) { 1119 | return v; 1120 | }; 1121 | 1122 | /** 1123 | * 变换两个参数的函数到多个参数 1124 | * @param {Function} fn 基函数 1125 | * @return {Function} 变换后的函数 1126 | * @demo 1127 | * function add(x, y) { return x+y; } 1128 | * add = redo(add); 1129 | * add(1,2,3) => 6 1130 | */ 1131 | var redo = function redo(fn) { 1132 | return function () { 1133 | var args = arguments; 1134 | var ret = fn(args[0], args[1]); 1135 | for (var i = 2, l = args.length; i < l; i++) { 1136 | ret = fn(ret, args[i]); 1137 | } 1138 | return ret; 1139 | }; 1140 | }; 1141 | 1142 | var random = Math.random; 1143 | var floor = Math.floor; 1144 | var makeRandom = function makeRandom() { 1145 | return floor(random() * 9e9); 1146 | }; 1147 | 1148 | var absoluteUrlReg = /^(https?:)?\/\//; 1149 | var isAbsoluteUrl = function isAbsoluteUrl(url) { 1150 | return !!url.match(absoluteUrlReg); 1151 | }; 1152 | 1153 | var relativeUrlReg = /^[\.\/]/; 1154 | var isRelativeUrl = function isRelativeUrl(url) { 1155 | return !!url.match(relativeUrlReg); 1156 | }; 1157 | 1158 | var BOOLEAN = 'boolean'; 1159 | var isBoolean = function isBoolean(v) { 1160 | return typeof v === BOOLEAN; 1161 | }; 1162 | 1163 | var STRING = 'string'; 1164 | var isString = function isString(v) { 1165 | return typeof v === STRING; 1166 | }; 1167 | 1168 | var FUNCTION = 'function'; 1169 | var isFunction = function isFunction(v) { 1170 | return typeof v === FUNCTION; 1171 | }; 1172 | 1173 | var runAsFn = function runAsFn(v) { 1174 | return isFunction(v) ? v() : v; 1175 | }; 1176 | 1177 | var NUMBER = 'number'; 1178 | var isNumber = function isNumber(v) { 1179 | return !isNaN(v) && typeof v === NUMBER; 1180 | }; 1181 | 1182 | var OBJECT = 'object'; 1183 | var isObject = function isObject(v) { 1184 | return typeof v === OBJECT; 1185 | }; 1186 | 1187 | var isWindow = function isWindow(v) { 1188 | return v !== NULL && v === v.window; 1189 | }; 1190 | 1191 | // 参考了zepto 1192 | var isPlainObject = function isPlainObject(v) { 1193 | return v !== NULL && isObject(v) && !isWindow(v) && Object.getPrototypeOf(v) === Object.prototype; 1194 | }; 1195 | 1196 | var isEmptyObject = function isEmptyObject(v) { 1197 | var count = 0; 1198 | for (var i in v) { 1199 | if (v.hasOwnProperty(i)) { 1200 | count++; 1201 | } 1202 | } 1203 | return count === 0; 1204 | }; 1205 | 1206 | var isArray = Array.isArray; 1207 | if (false) { 1208 | if (!isArray) { 1209 | isArray = function (v) { 1210 | return toString.call(v) === ARRAY_TYPE; 1211 | }; 1212 | } 1213 | } 1214 | 1215 | /** 1216 | * 判断是否跨域 1217 | * @type {Element} 1218 | * @note 需要特别关注IE8~11的行为是不一样的!!! 1219 | */ 1220 | var originA = undefined; 1221 | if (doc) { 1222 | originA = doc.createElement('a'); 1223 | originA.href = location.href; 1224 | } 1225 | var isCrossDomain = function isCrossDomain(url) { 1226 | 1227 | var requestA = doc.createElement('a'); 1228 | requestA.href = url; 1229 | //console.log(originA.protocol + '//' + originA.host + '\n' + requestA.protocol + '//' + requestA.host); 1230 | 1231 | // 如果`url`的值不包含`protocol`和`host`(比如相对路径), 在标准浏览器下, 会自定补全`requestA`对象的`protocal`和`host`属性. 1232 | // 但在IE8~11下, 不会自动补全. 即`requestA.protocol`和`requestA.host`的值都是空的. 1233 | // 在IE11的不同小版本下, requestA.protocol的值有的是`:`, 有的是空字符串, 太奇葩啦! 1234 | if (false) { 1235 | if (isIE && (requestA.protocol === ':' || requestA.protocol === '')) { 1236 | if (requestA.hostname === '') { 1237 | //alert(0) 1238 | return false; 1239 | } else { 1240 | //alert('1:'+(originA.hostname !== requestA.hostname || originA.port !== requestA.port)) 1241 | return originA.hostname !== requestA.hostname || originA.port !== requestA.port; 1242 | } 1243 | } 1244 | } 1245 | 1246 | //let log = { 1247 | // 'originA.hostname': originA.hostname, 1248 | // 'requestA.hostname': requestA.hostname, 1249 | // 'originA.port': originA.port, 1250 | // 'requestA.port': requestA.port, 1251 | // 'originA.protocol': originA.protocol, 1252 | // 'requestA.protocol': requestA.protocol 1253 | //} 1254 | // 1255 | //alert(JSON.stringify(log)); 1256 | 1257 | // 标准浏览器 1258 | return originA.hostname !== requestA.hostname || originA.port !== requestA.port || originA.protocol !== requestA.protocol; 1259 | }; 1260 | 1261 | /** 1262 | * 对象扩展 1263 | * @param {Object} receiver 1264 | * @param {Object} supplier 1265 | * @return {Object} 扩展后的receiver对象 1266 | * @note 这个extend方法是定制的, 不要拷贝到其他地方用!!! 1267 | * @note 这个extend方法是深拷贝方式的!!! 1268 | * TODO 1269 | */ 1270 | var extend = function extend() { 1271 | var receiver = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; 1272 | var supplier = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; 1273 | var deepCopy = arguments.length <= 2 || arguments[2] === undefined ? FALSE : arguments[2]; 1274 | 1275 | for (var key in supplier) { 1276 | // `supplier`中不是未定义的键 都可以执行扩展 1277 | if (supplier.hasOwnProperty(key) && supplier[key] !== undefined) { 1278 | if (deepCopy === TRUE) { 1279 | if (isArray(supplier[key])) { 1280 | receiver[key] = [].concat(supplier[key]); 1281 | } else if (isPlainObject(supplier[key])) { 1282 | receiver[key] = extend({}, supplier[key]); 1283 | } else { 1284 | receiver[key] = supplier[key]; 1285 | } 1286 | } else { 1287 | receiver[key] = supplier[key]; 1288 | } 1289 | } 1290 | } 1291 | return receiver; 1292 | }; 1293 | 1294 | var likeArray = function likeArray(v) { 1295 | if (!v) { 1296 | return false; 1297 | } 1298 | return typeof v.length === NUMBER; 1299 | }; 1300 | 1301 | /** 1302 | * 1303 | * @param v {Array|Object} 遍历目标对象 1304 | * @param fn {Function} 遍历器 会被传入两个参数, 分别是`value`和`key` 1305 | */ 1306 | var each = function each(v, fn) { 1307 | var i = undefined, 1308 | l = undefined; 1309 | if (likeArray(v)) { 1310 | for (i = 0, l = v.length; i < l; i++) { 1311 | if (fn.call(v[i], v[i], i) === false) return; 1312 | } 1313 | } else { 1314 | for (i in v) { 1315 | if (fn.call(v[i], v[i], i) === false) return; 1316 | } 1317 | } 1318 | }; 1319 | 1320 | /** 1321 | * 将对象的`键`排序后 返回一个新对象 1322 | * 1323 | * @param obj {Object} 被操作的对象 1324 | * @returns {Object} 返回的新对象 1325 | * @case 这个函数用于对比两次请求的参数是否一致 1326 | */ 1327 | var sortPlainObjectKey = function sortPlainObjectKey(obj) { 1328 | var clone = {}; 1329 | var key = undefined; 1330 | var keyArray = []; 1331 | for (key in obj) { 1332 | if (obj.hasOwnProperty(key)) { 1333 | keyArray.push(key); 1334 | if (isPlainObject(obj[key])) { 1335 | obj[key] = sortPlainObjectKey(obj[key]); 1336 | } 1337 | } 1338 | } 1339 | keyArray.sort(); 1340 | for (var i = 0, l = keyArray.length; i < l; i++) { 1341 | clone[keyArray[i]] = obj[keyArray[i]]; 1342 | } 1343 | return clone; 1344 | }; 1345 | 1346 | var serialize = function serialize(params, obj, traditional, scope) { 1347 | var type = undefined, 1348 | array = isArray(obj), 1349 | hash = isPlainObject(obj); 1350 | each(obj, function (value, key) { 1351 | type = toString.call(value); 1352 | if (scope) { 1353 | key = traditional ? scope : scope + '[' + (hash || type == OBJECT_TYPE || type == ARRAY_TYPE ? key : '') + ']'; 1354 | } 1355 | 1356 | // 递归 1357 | if (!scope && array) { 1358 | params.add(value.name, value.value); 1359 | } 1360 | // recurse into nested objects 1361 | else if (type == ARRAY_TYPE || !traditional && type == OBJECT_TYPE) { 1362 | serialize(params, value, traditional, key); 1363 | } else { 1364 | params.add(key, value); 1365 | } 1366 | }); 1367 | }; 1368 | 1369 | /** 1370 | * 功能和`Zepto.param`一样 1371 | * @param obj {Object} 1372 | * @param traditional {Boolean} 1373 | * @returns {string} 1374 | * $.param({ foo: { one: 1, two: 2 }}) // "foo[one]=1&foo[two]=2)" 1375 | * $.param({ ids: [1,2,3] }) // "ids[]=1&ids[]=2&ids[]=3" 1376 | * $.param({ ids: [1,2,3] }, true) // "ids=1&ids=2&ids=3" 1377 | * $.param({ foo: 'bar', nested: { will: 'not be ignored' }}) // "foo=bar&nested[will]=not+be+ignored" 1378 | * $.param({ foo: 'bar', nested: { will: 'be ignored' }}, true) // "foo=bar&nested=[object+Object]" 1379 | * $.param({ id: function(){ return 1 + 2 } }) // "id=3" 1380 | */ 1381 | var param = function param(obj, traditional) { 1382 | var params = []; 1383 | params.add = function (key, value) { 1384 | if (isFunction(value)) value = value(); 1385 | if (value == NULL) value = ''; 1386 | params.push(escape(key) + '=' + escape(value)); 1387 | }; 1388 | serialize(params, obj, traditional); 1389 | return params.join('&').replace(/%20/g, '+'); 1390 | }; 1391 | 1392 | var decodeParam = function decodeParam(str) { 1393 | return decodeURIComponent(str.replace(/\+/g, ' ')); 1394 | }; 1395 | 1396 | // 给URL追加查询字符串 1397 | var appendQueryString = function appendQueryString(url, obj, traditional) { 1398 | var queryString = param(obj, traditional); 1399 | 1400 | if (queryString) { 1401 | return url + (~url.indexOf('?') ? '&' : '?') + queryString; 1402 | } else { 1403 | return url; 1404 | } 1405 | }; 1406 | 1407 | module.exports = { 1408 | appendQueryString: appendQueryString, 1409 | decodeParam: decodeParam, 1410 | dummyPromise: dummyPromise, 1411 | each: each, 1412 | extend: redo(extend), 1413 | isAbsoluteUrl: isAbsoluteUrl, 1414 | isArray: isArray, 1415 | isBoolean: isBoolean, 1416 | isCrossDomain: isCrossDomain, 1417 | isEmptyObject: isEmptyObject, 1418 | isFunction: isFunction, 1419 | isIE: isIE, 1420 | isNumber: isNumber, 1421 | isPlainObject: isPlainObject, 1422 | isRelativeUrl: isRelativeUrl, 1423 | isString: isString, 1424 | makeRandom: makeRandom, 1425 | noop: noop, 1426 | sortPlainObjectKey: sortPlainObjectKey, 1427 | param: param, 1428 | runAsFn: runAsFn 1429 | }; 1430 | 1431 | /***/ }, 1432 | /* 5 */ 1433 | /***/ function(module, exports, __webpack_require__) { 1434 | 1435 | 'use strict'; 1436 | 1437 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } 1438 | 1439 | var _require = __webpack_require__(4); 1440 | 1441 | var appendQueryString = _require.appendQueryString; 1442 | var noop = _require.noop; 1443 | var extend = _require.extend; 1444 | var makeRandom = _require.makeRandom; 1445 | 1446 | var hasWindow = 'undefined' !== typeof window; 1447 | var win = hasWindow ? window : null; 1448 | var doc = hasWindow ? document : null; 1449 | var NULL = null; 1450 | var SCRIPT = 'script'; 1451 | var FALSE = false; 1452 | 1453 | var removeScript = function removeScript(script) { 1454 | script.onerror = NULL; 1455 | script.parentNode.removeChild(script); 1456 | script = NULL; 1457 | }; 1458 | var head = NULL; 1459 | var insertScript = function insertScript(url, options) { 1460 | var script = doc.createElement(SCRIPT); 1461 | script.src = url; 1462 | script.async = true; 1463 | 1464 | script.onerror = function (e) { 1465 | win[options.callbackName] = NULL; 1466 | options.error(e); 1467 | options.complete(); 1468 | }; 1469 | 1470 | head = head || doc.getElementsByTagName('head')[0]; 1471 | head.insertBefore(script, head.firstChild); 1472 | return script; 1473 | }; 1474 | 1475 | var defaultOptions = { 1476 | url: '', 1477 | mark: {}, 1478 | data: {}, 1479 | success: noop, 1480 | error: noop, 1481 | complete: noop, 1482 | log: false, 1483 | flag: 'callback', 1484 | callbackName: 'jsonp{id}', 1485 | traditional: FALSE 1486 | }; 1487 | 1488 | var jsonp = function jsonp(options) { 1489 | 1490 | options = extend({}, defaultOptions, options); 1491 | 1492 | var callbackName = options.callbackName = options.callbackName.replace(/\{id\}/, makeRandom()); 1493 | 1494 | var originComplete = options.complete; 1495 | 1496 | var script = undefined; 1497 | 1498 | // 二次包装的`complete`回调 1499 | options.complete = function () { 1500 | // 删除脚本 1501 | removeScript(script); 1502 | originComplete(); 1503 | }; 1504 | 1505 | // 成功回调 1506 | win[callbackName] = function (data) { 1507 | win[callbackName] = NULL; 1508 | options.success(data); 1509 | options.complete(); 1510 | }; 1511 | 1512 | // 生成`url` 1513 | var url = appendQueryString(options.url, extend(_defineProperty({}, options.flag, callbackName), options.mark, options.data), options.traditional); 1514 | 1515 | // 插入脚本 1516 | script = insertScript(url, options); 1517 | 1518 | return { 1519 | abort: function abort() { 1520 | // 覆盖成功回调为无数据处理版本 1521 | win[callbackName] = function () { 1522 | win[callbackName] = NULL; 1523 | }; 1524 | removeScript(script); 1525 | } 1526 | }; 1527 | }; 1528 | 1529 | module.exports = jsonp; 1530 | 1531 | /***/ }, 1532 | /* 6 */ 1533 | /***/ function(module, exports) { 1534 | 1535 | "use strict"; 1536 | 1537 | function Defer() { 1538 | var t = this; 1539 | t.promise = new Promise(function (resolve, reject) { 1540 | t._resolve = resolve; 1541 | t._reject = reject; 1542 | }); 1543 | } 1544 | Defer.prototype.resolve = function (value) { 1545 | this._resolve.call(this.promise, value); 1546 | }; 1547 | Defer.prototype.reject = function (reason) { 1548 | this._reject.call(this.promise, reason); 1549 | }; 1550 | module.exports = Defer; 1551 | 1552 | /***/ }, 1553 | /* 7 */ 1554 | /***/ function(module, exports) { 1555 | 1556 | 'use strict'; 1557 | 1558 | var pre = '__'; 1559 | 1560 | var Event = { 1561 | on: function on() { 1562 | var t = this; 1563 | var args = arguments; 1564 | if (typeof args[0] === 'string' && typeof args[1] === 'function') { 1565 | var type = rename(args[0]); 1566 | t[type] = t[type] || []; 1567 | t[type].push(args[1]); 1568 | } else if (typeof args[0] === 'object') { 1569 | var hash = args[0]; 1570 | for (var i in hash) { 1571 | t.on(i, hash[i]); 1572 | } 1573 | } 1574 | }, 1575 | off: function off(type, fn) { 1576 | var t = this; 1577 | var type = rename(type); 1578 | if (!fn) { 1579 | delete t[type]; 1580 | } else { 1581 | var fns = t[type]; 1582 | fns.splice(fns.indexOf(fn), 1); 1583 | if (!t[type].length) delete t[type]; 1584 | } 1585 | }, 1586 | // @param {array} args 1587 | fire: function fire(type, args, context) { 1588 | var t = this; 1589 | var fns = t[rename(type)]; 1590 | if (!fns) return 'NO_EVENT'; 1591 | for (var i = 0, fn; fn = fns[i]; i++) { 1592 | fn.apply(context || t, [].concat(args)); 1593 | } 1594 | }, 1595 | hasEvent: function hasEvent(type) { 1596 | return !!this[rename(type)]; 1597 | } 1598 | }; 1599 | 1600 | function rename(type) { 1601 | return pre + type; 1602 | } 1603 | 1604 | module.exports = Event; 1605 | 1606 | /***/ }, 1607 | /* 8 */ 1608 | /***/ function(module, exports, __webpack_require__) { 1609 | 1610 | /** 1611 | * 创建轮询支持 1612 | * @param api {Function} 需要轮询的函数 1613 | */ 1614 | 'use strict'; 1615 | 1616 | var FALSE = false; 1617 | var TRUE = true; 1618 | var NULL = null; 1619 | 1620 | var _require = __webpack_require__(4); 1621 | 1622 | var isNumber = _require.isNumber; 1623 | var noop = _require.noop; 1624 | 1625 | /** 1626 | * 创建轮询支持 1627 | * @param api {Function} 需要轮询的函数 1628 | */ 1629 | module.exports = function (api) { 1630 | var loopTimer = NULL; 1631 | api.looping = FALSE; 1632 | // 开启轮询 1633 | api.startLoop = function (options) { 1634 | var resolveFn = arguments.length <= 1 || arguments[1] === undefined ? noop : arguments[1]; 1635 | var rejectFn = arguments.length <= 2 || arguments[2] === undefined ? noop : arguments[2]; 1636 | 1637 | if (!options.duration || !isNumber(options.duration)) { 1638 | throw new Error('Illegal `duration` value for `startLoop` method.'); 1639 | return api; 1640 | } 1641 | 1642 | var sleepAndRequest = function sleepAndRequest() { 1643 | api.looping = TRUE; 1644 | loopTimer = setTimeout(function () { 1645 | api(options.data).then(resolveFn, rejectFn); 1646 | sleepAndRequest(); 1647 | }, options.duration); 1648 | }; 1649 | 1650 | // NOTE 轮询过程中是不响应服务器端错误的 所以第二个参数是`noop` 1651 | api(options.data).then(resolveFn, rejectFn); 1652 | sleepAndRequest(); 1653 | }; 1654 | // 停止轮询 1655 | api.stopLoop = function () { 1656 | clearTimeout(loopTimer); 1657 | api.looping = FALSE; 1658 | loopTimer = NULL; 1659 | }; 1660 | }; 1661 | 1662 | /***/ }, 1663 | /* 9 */ 1664 | /***/ function(module, exports, __webpack_require__) { 1665 | 1666 | 'use strict'; 1667 | 1668 | var FALSE = false; 1669 | var TRUE = true; 1670 | 1671 | var _require = __webpack_require__(4); 1672 | 1673 | var noop = _require.noop; 1674 | var isEmptyObject = _require.isEmptyObject; 1675 | var sortPlainObjectKey = _require.sortPlainObjectKey; 1676 | var extend = _require.extend; 1677 | var runAsFn = _require.runAsFn; 1678 | 1679 | module.exports = function (api) { 1680 | var t = this; 1681 | var config = api.config; 1682 | 1683 | api.soon = function (data) { 1684 | var successFn = arguments.length <= 1 || arguments[1] === undefined ? noop : arguments[1]; 1685 | var errorFn = arguments.length <= 2 || arguments[2] === undefined ? noop : arguments[2]; 1686 | 1687 | // 是否忽略自身的并发请求 1688 | // NOTE 这个地方和内置的api方法不一致 1689 | if (config.ignoreSelfConcurrent && config.pending) { 1690 | return; 1691 | } 1692 | 1693 | if (config.overrideSelfConcurrent && config._lastRequester) { 1694 | config._lastRequester.abort(); 1695 | delete config._lastRequester; 1696 | } 1697 | 1698 | // 一次请求的私有相关数据 1699 | var vars = t.makeVars(data); 1700 | 1701 | if (api.storageUseable) { 1702 | 1703 | // 只有GET和JSONP才会有storage生效 1704 | vars.queryString = isEmptyObject(vars.data) ? 'no-query-string' : JSON.stringify(sortPlainObjectKey(vars.data)); 1705 | 1706 | api.storage.has(vars.queryString).then(function (result) { 1707 | // console.warn('has cached: ', hasValue); 1708 | if (result.has) { 1709 | // 调用 willFetch 钩子 1710 | config.willFetch(vars, config, 'storage'); 1711 | successFn({ 1712 | fromStorage: TRUE, 1713 | content: result.value 1714 | }); 1715 | } 1716 | 1717 | // 在`storage`可用的情况下, 远程请求返回的数据会同步到`storage` 1718 | return t.remoteRequest(vars, config); 1719 | }).then(function (content) { 1720 | successFn({ 1721 | fromStorage: FALSE, 1722 | content: content 1723 | }); 1724 | })['catch'](function (e) { 1725 | errorFn(e); 1726 | }); 1727 | } else { 1728 | t.remoteRequest(vars, config).then(function (content) { 1729 | successFn({ 1730 | fromStorage: FALSE, 1731 | content: content 1732 | }); 1733 | })['catch'](function (e) { 1734 | errorFn(e); 1735 | }); 1736 | } 1737 | }; 1738 | }; 1739 | 1740 | /***/ } 1741 | /******/ ]) 1742 | }); 1743 | ; 1744 | 1745 | /***/ }, 1746 | /* 2 */ 1747 | /***/ function(module, exports, __webpack_require__) { 1748 | 1749 | (function webpackUniversalModuleDefinition(root, factory) { 1750 | if(true) 1751 | module.exports = factory(); 1752 | else if(typeof define === 'function' && define.amd) 1753 | define("nattyStorage", [], factory); 1754 | else if(typeof exports === 'object') 1755 | exports["nattyStorage"] = factory(); 1756 | else 1757 | root["nattyStorage"] = factory(); 1758 | })(this, function() { 1759 | return /******/ (function(modules) { // webpackBootstrap 1760 | /******/ // The module cache 1761 | /******/ var installedModules = {}; 1762 | /******/ 1763 | /******/ // The require function 1764 | /******/ function __webpack_require__(moduleId) { 1765 | /******/ 1766 | /******/ // Check if module is in cache 1767 | /******/ if(installedModules[moduleId]) 1768 | /******/ return installedModules[moduleId].exports; 1769 | /******/ 1770 | /******/ // Create a new module (and put it into the cache) 1771 | /******/ var module = installedModules[moduleId] = { 1772 | /******/ exports: {}, 1773 | /******/ id: moduleId, 1774 | /******/ loaded: false 1775 | /******/ }; 1776 | /******/ 1777 | /******/ // Execute the module function 1778 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 1779 | /******/ 1780 | /******/ // Flag the module as loaded 1781 | /******/ module.loaded = true; 1782 | /******/ 1783 | /******/ // Return the exports of the module 1784 | /******/ return module.exports; 1785 | /******/ } 1786 | /******/ 1787 | /******/ 1788 | /******/ // expose the modules object (__webpack_modules__) 1789 | /******/ __webpack_require__.m = modules; 1790 | /******/ 1791 | /******/ // expose the module cache 1792 | /******/ __webpack_require__.c = installedModules; 1793 | /******/ 1794 | /******/ // __webpack_public_path__ 1795 | /******/ __webpack_require__.p = ""; 1796 | /******/ 1797 | /******/ // Load entry module and return exports 1798 | /******/ return __webpack_require__(0); 1799 | /******/ }) 1800 | /************************************************************************/ 1801 | /******/ ([ 1802 | /* 0 */ 1803 | /***/ function(module, exports, __webpack_require__) { 1804 | 1805 | 'use strict'; 1806 | 1807 | module.exports = __webpack_require__(1); 1808 | 1809 | /***/ }, 1810 | /* 1 */ 1811 | /***/ function(module, exports, __webpack_require__) { 1812 | 1813 | "use strict"; 1814 | 1815 | var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 1816 | 1817 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 1818 | 1819 | var _require = __webpack_require__(2); 1820 | 1821 | var extend = _require.extend; 1822 | var isPlainObject = _require.isPlainObject; 1823 | var noop = _require.noop; 1824 | 1825 | var win = window; 1826 | var hasWindow = 'undefined' !== typeof win; 1827 | var NULL = null; 1828 | var EMPTY = ''; 1829 | var TRUE = true; 1830 | var FALSE = !TRUE; 1831 | var PLACEHOLDER = '_placeholder'; 1832 | 1833 | var VERSION = undefined; 1834 | (VERSION = "1.0.0"); 1835 | 1836 | var support = { 1837 | localStorage: hasWindow && !!win.localStorage && test('localStorage'), 1838 | sessionStorage: hasWindow && !!win.sessionStorage && test('sessionStorage') 1839 | }; 1840 | 1841 | function test(type) { 1842 | var data = { 'x': 'x' }; 1843 | var key = 'natty-storage-test'; 1844 | var tester = createStorage(type); 1845 | tester.set(key, data); 1846 | var useable = JSON.stringify(tester.get(key)) === JSON.stringify(data); 1847 | tester.remove(key); 1848 | return useable; 1849 | } 1850 | 1851 | // 全局默认配置 1852 | var defaultGlobalConfig = { 1853 | // localStorage, sessionStorage, variable 1854 | type: 'localStorage', 1855 | 1856 | // 存到浏览器缓存中使用的键 1857 | key: EMPTY, 1858 | 1859 | // 版本号 1860 | tag: EMPTY, 1861 | 1862 | // 有效期长, 单位ms 1863 | duration: 0, 1864 | 1865 | // 有效期至, 时间戳 1866 | until: 0, 1867 | 1868 | // 是否以异步方式使用set/get/has/remove 1869 | async: false 1870 | }; 1871 | 1872 | // 运行时的全局配置 1873 | var runtimeGlobalConfig = extend({}, defaultGlobalConfig); 1874 | 1875 | /** 1876 | * let ls = new nattyStorage({ 1877 | * type: 'localstorage', // sessionstorage, variable 1878 | * key: 'city', 1879 | * // 验证是否有效,如果是首次创建该LS,则不执行验证 1880 | * id: '1.0' 1881 | * }) 1882 | */ 1883 | 1884 | var Storage = (function () { 1885 | /** 1886 | * 构造函数 1887 | * @param options 1888 | */ 1889 | 1890 | function Storage() { 1891 | var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; 1892 | 1893 | _classCallCheck(this, Storage); 1894 | 1895 | var t = this; 1896 | 1897 | t.config = extend({}, runtimeGlobalConfig, options); 1898 | 1899 | // 必须配置`key`!!! 无论什么类型!!! 1900 | if (!t.config.key) { 1901 | throw new Error('`key` is missing, please check the options passed in `nattyStorage` constructor.'); 1902 | } 1903 | 1904 | t._storage = support[t.config.type] ? createStorage(t.config.type) : createVariable(); 1905 | 1906 | t._CHECK_KEY = 'nattyStorageCheck:' + t.config.key; 1907 | t._DATA_KEY = 'nattyStorageData:' + t.config.key; 1908 | t._placeholderUsed = FALSE; 1909 | 1910 | // 每个`storage`实例对象都是全新的, 只有`storage`实例的数据才可能是缓存的. 1911 | t._createStamp = +new Date(); 1912 | } 1913 | 1914 | /** 1915 | * 惰性初始化 在首次调用`set、get、remove`方法时才执行一次 且只执行一次 1916 | * @private 1917 | * @note 为什么要做惰性初始化, 因为 1918 | */ 1919 | 1920 | _createClass(Storage, [{ 1921 | key: '_lazyInit', 1922 | value: function _lazyInit() { 1923 | var t = this; 1924 | 1925 | t._checkData = t._storage.get(t._CHECK_KEY); 1926 | 1927 | // 当前`key`的`storage`是否已经存在 1928 | t._isNew = t._checkData === NULL; 1929 | // console.log('is new t._checkData', t._isNew); 1930 | 1931 | // 没有对应的本地缓存 或 本地缓存已过期 则 创建新的`storage`实例 1932 | if (t._isNew || t.isOutdated()) { 1933 | // console.log('create new t._checkData'); 1934 | // 新的数据内容 1935 | t._storage.set(t._DATA_KEY, t._data = {}); 1936 | } 1937 | // 使用已有的本地缓存 1938 | else { 1939 | // console.log('use cached t._checkData'); 1940 | t._data = t._storage.get(t._DATA_KEY); 1941 | if (t._data === NULL) { 1942 | t._storage.set(t._DATA_KEY, t._data = {}); 1943 | } 1944 | } 1945 | 1946 | // 更新验证数据 1947 | t._storage.set(t._CHECK_KEY, t._checkData = { 1948 | id: t.config.id, 1949 | lastUpdate: t._createStamp, 1950 | duration: t.config.duration, 1951 | until: t.config.until 1952 | }); 1953 | } 1954 | 1955 | /** 1956 | * 判断当前`key`的`storage`是否已经过期 1957 | * @returns {boolean} 1958 | */ 1959 | }, { 1960 | key: 'isOutdated', 1961 | value: function isOutdated() { 1962 | var t = this; 1963 | if (t.config.id && t.config.id !== t._checkData.id) { 1964 | return TRUE; 1965 | } 1966 | 1967 | var now = +new Date(); 1968 | // 注意要使用`_checkData`的`duration`验证, 而不是用`config`的`duration`验证!! 1969 | if (t._checkData.duration && now - t._checkData.lastUpdate > t._checkData.duration) { 1970 | return TRUE; 1971 | } 1972 | 1973 | if (t._checkData.until && now > t._checkData.until) { 1974 | return TRUE; 1975 | } 1976 | 1977 | // console.log('outdated: false'); 1978 | return FALSE; 1979 | } 1980 | 1981 | /** 1982 | * 设置指指定路径的数据 1983 | * @param path {Any} optional 要设置的值的路径 或 要设置的完整值 1984 | * @param value {Any} 值 1985 | * 1986 | * instance.set(object) 1987 | * instance.set('foo', any-type) 1988 | * instance.set('foo.bar', any-type) 1989 | * @note ls.set('x') 则 整个值为 'x' 1990 | */ 1991 | }, { 1992 | key: 'set', 1993 | value: function set(path, data) { 1994 | 1995 | var t = this; 1996 | var argumentLength = arguments.length; 1997 | 1998 | var todo = function todo(resolve, reject) { 1999 | try { 2000 | if (!t._data) { 2001 | t._lazyInit(); 2002 | } 2003 | 2004 | if (argumentLength === 1) { 2005 | if (isPlainObject(path)) { 2006 | t._data = path; 2007 | } else { 2008 | t._data[PLACEHOLDER] = path; 2009 | t._placeholderUsed = TRUE; 2010 | } 2011 | } else { 2012 | setValueByPath(path, data, t._data); 2013 | } 2014 | t._storage.set(t._DATA_KEY, t._data); 2015 | resolve(); 2016 | } catch (e) { 2017 | reject(e); 2018 | } 2019 | }; 2020 | 2021 | if (t.config.async) { 2022 | return new Promise(todo); 2023 | } else { 2024 | todo(noop, throwError); 2025 | } 2026 | } 2027 | 2028 | /** 2029 | * 获取指定的路径的数据 2030 | * @param path {String} optional 要获取的值的路径 如果不传 则返回整体值 2031 | * @returns {ny} 2032 | * 2033 | * instance.get() 2034 | * instance.get('foo') 2035 | * instance.get('foo.bar') 2036 | */ 2037 | }, { 2038 | key: 'get', 2039 | value: function get(path) { 2040 | var t = this; 2041 | var data = undefined; 2042 | var todo = function todo(resolve, reject) { 2043 | try { 2044 | if (!t._data) { 2045 | t._lazyInit(); 2046 | } 2047 | 2048 | if (path) { 2049 | data = getValueByPath(path, t._data); 2050 | } else if (t._placeholderUsed) { 2051 | data = t._data[PLACEHOLDER]; 2052 | } else { 2053 | data = t._data; 2054 | } 2055 | resolve(data); 2056 | } catch (e) { 2057 | reject(e); 2058 | } 2059 | }; 2060 | 2061 | if (t.config.async) { 2062 | return new Promise(todo); 2063 | } else { 2064 | todo(noop, throwError); 2065 | return data; 2066 | } 2067 | } 2068 | 2069 | /** 2070 | * 返回指定的路径是否有值 2071 | * @param path {String} optional 要查询的路径 2072 | * @returns {Promise} 2073 | */ 2074 | }, { 2075 | key: 'has', 2076 | value: function has(path) { 2077 | var t = this; 2078 | var result = undefined; 2079 | var todo = function todo(resolve, reject) { 2080 | try { 2081 | if (!t._data) { 2082 | t._lazyInit(); 2083 | } 2084 | 2085 | // 如果有数据 且 没有使用内置`placeholder`, 说明是使用`path`方式设置的值 2086 | if (!t._placeholderUsed && !isEmptyPlainObject(t._data)) { 2087 | if (!path) { 2088 | throw new Error('a `path` argument should be passed into the `has` method'); 2089 | } 2090 | 2091 | result = hasValueByPath(path, t._data) ? { 2092 | has: true, 2093 | value: getValueByPath(path, t._data) 2094 | } : {}; 2095 | 2096 | resolve(result); 2097 | } else { 2098 | result = t._data.hasOwnProperty(PLACEHOLDER) ? { 2099 | has: true, 2100 | value: t._data[PLACEHOLDER] 2101 | } : {}; 2102 | resolve(result); 2103 | } 2104 | } catch (e) { 2105 | reject(e); 2106 | } 2107 | }; 2108 | 2109 | if (t.config.async) { 2110 | return new Promise(todo); 2111 | } else { 2112 | todo(noop, throwError); 2113 | return result; 2114 | } 2115 | } 2116 | 2117 | /** 2118 | * 删除指定的路径的数据, 包括键本身 2119 | * @param path {String} optional 要获取的值的路径 如果不传 则返回整体值 2120 | */ 2121 | }, { 2122 | key: 'remove', 2123 | value: function remove(path) { 2124 | var t = this; 2125 | var todo = function todo(resolve, reject) { 2126 | try { 2127 | if (!t._data) { 2128 | t._lazyInit(); 2129 | } 2130 | if (path) { 2131 | removeKeyAndValueByPath(path, t._data); 2132 | t._storage.set(t._DATA_KEY, t._data); 2133 | } else { 2134 | // 删除所有数据, 复原到初始空对象 2135 | t.set({}); 2136 | } 2137 | resolve(); 2138 | } catch (e) { 2139 | reject(e); 2140 | } 2141 | }; 2142 | 2143 | if (t.config.async) { 2144 | return new Promise(todo); 2145 | } else { 2146 | todo(noop, throwError); 2147 | } 2148 | } 2149 | 2150 | /** 2151 | * 销毁当前`storage`实例 2152 | */ 2153 | }, { 2154 | key: 'destroy', 2155 | value: function destroy() { 2156 | var t = this; 2157 | t._storage.remove(t._CHECK_KEY); 2158 | t._storage.remove(t._DATA_KEY); 2159 | } 2160 | }, { 2161 | key: 'dump', 2162 | value: function dump() { 2163 | if (JSON && console) { 2164 | console.log(JSON.stringify(this._data, NULL, 4)); 2165 | } 2166 | } 2167 | }]); 2168 | 2169 | return Storage; 2170 | })(); 2171 | 2172 | var nattyStorage = function nattyStorage(options) { 2173 | return new Storage(options); 2174 | }; 2175 | 2176 | nattyStorage.version = VERSION; 2177 | nattyStorage._variable = variable; 2178 | nattyStorage.support = support; 2179 | 2180 | /** 2181 | * 执行全局配置 2182 | * @param options 2183 | */ 2184 | nattyStorage.setGlobal = function (options) { 2185 | runtimeGlobalConfig = extend({}, defaultGlobalConfig, options); 2186 | return undefined; 2187 | }; 2188 | 2189 | /** 2190 | * 获取全局配置 2191 | * @param property {String} optional 2192 | * @returns {*} 2193 | */ 2194 | nattyStorage.getGlobal = function (property) { 2195 | return property ? runtimeGlobalConfig[property] : runtimeGlobalConfig; 2196 | }; 2197 | 2198 | function throwError(e) { 2199 | throw new Error(e); 2200 | } 2201 | 2202 | function createStorage(storage) { 2203 | storage = win[storage]; 2204 | return { 2205 | // NOTE 值为undefined的情况, JSON.stringify方法会将键删除 2206 | // JSON.stringify({x:undefined}) === "{}" 2207 | set: function set(key, value) { 2208 | // TODO 看看safari是否还有bug 2209 | // storage.removeItem(key); 2210 | storage.setItem(key, JSON.stringify(value)); 2211 | }, 2212 | get: function get(key) { 2213 | var value = storage.getItem(key); 2214 | // alert(localStorage[key]); 2215 | if (!value) return NULL; 2216 | try { 2217 | value = JSON.parse(value); 2218 | } catch (e) {} 2219 | return value; 2220 | }, 2221 | remove: function remove(key) { 2222 | storage.removeItem(key); 2223 | } 2224 | }; 2225 | } 2226 | 2227 | var variable = {}; 2228 | function createVariable() { 2229 | var storage = variable; 2230 | return { 2231 | set: function set(key, value) { 2232 | storage[key] = value; 2233 | }, 2234 | get: function get(key) { 2235 | // 当对应的键不存在时, 返回值保持和`storage`一致。 2236 | if (!(key in storage)) { 2237 | return NULL; 2238 | } 2239 | return storage[key]; 2240 | }, 2241 | remove: function remove(key) { 2242 | delete storage[key]; 2243 | } 2244 | }; 2245 | } 2246 | 2247 | function reserveString(str) { 2248 | return str.split(EMPTY).reverse().join(EMPTY); 2249 | } 2250 | 2251 | function splitPathToKeys(path) { 2252 | var ret; 2253 | if (path.indexOf('\\.') === -1) { 2254 | ret = path.split('.'); 2255 | } else { 2256 | ret = reserveString(path).split(/\.(?!\\)/).reverse(); 2257 | for (var i = 0, l = ret.length; i < l; i++) { 2258 | ret[i] = reserveString(ret[i].replace(/\.\\/g, '.')); 2259 | } 2260 | } 2261 | return ret; 2262 | } 2263 | 2264 | function setValueByPath(path, value, data) { 2265 | var keys = splitPathToKeys(path); 2266 | var bottomData = data; 2267 | while (keys.length) { 2268 | var key = keys.shift(); 2269 | if (keys.length) { 2270 | bottomData[key] = bottomData[key] || {}; 2271 | bottomData = bottomData[key]; 2272 | } else { 2273 | if (isPlainObject(bottomData)) { 2274 | bottomData[key] = value; 2275 | } else { 2276 | throw new Error('Cannot create property `' + key + '` on non-object value, path:`' + path + '`'); 2277 | } 2278 | } 2279 | } 2280 | return data; 2281 | } 2282 | 2283 | function getValueByPath(path, data, isKey) { 2284 | isKey = isKey || false; 2285 | if (isKey === true || path.indexOf('.') === -1) { 2286 | return data[path]; 2287 | } else { 2288 | var keys = splitPathToKeys(path); 2289 | 2290 | while (keys.length) { 2291 | var key = keys.shift(); 2292 | data = getValueByPath(key, data, true); 2293 | 2294 | if (typeof data !== 'object' || data === undefined) { 2295 | if (keys.length) data = undefined; 2296 | break; 2297 | } 2298 | } 2299 | return data; 2300 | } 2301 | } 2302 | 2303 | function hasValueByPath(path, data, isKey) { 2304 | // 首次调用, 如果没有`.`, 就是key的含义 2305 | isKey = isKey || path.indexOf('.') === -1; 2306 | if (isKey) { 2307 | return data.hasOwnProperty(path); 2308 | } else { 2309 | var keys = splitPathToKeys(path); 2310 | while (keys.length) { 2311 | var key = keys.shift(); 2312 | // console.log('check key: ', key); 2313 | var hasKey = data.hasOwnProperty(key); 2314 | if (hasKey && keys.length) { 2315 | data = getValueByPath(key, data, true); 2316 | if (!isPlainObject(data)) { 2317 | return FALSE; 2318 | } 2319 | } else { 2320 | return hasKey; 2321 | } 2322 | } 2323 | } 2324 | } 2325 | 2326 | function removeKeyAndValueByPath(path, data) { 2327 | var keys = splitPathToKeys(path); 2328 | var bottomData = data; 2329 | while (keys.length) { 2330 | var key = keys.shift(); 2331 | if (keys.length) { 2332 | bottomData[key] = bottomData[key] || {}; 2333 | bottomData = bottomData[key]; 2334 | } else { 2335 | delete bottomData[key]; 2336 | } 2337 | } 2338 | return data; 2339 | } 2340 | 2341 | function isEmptyPlainObject(v) { 2342 | var ret = TRUE; 2343 | for (var i in v) { 2344 | ret = FALSE; 2345 | break; 2346 | } 2347 | return ret; 2348 | } 2349 | 2350 | module.exports = nattyStorage; 2351 | 2352 | /***/ }, 2353 | /* 2 */ 2354 | /***/ function(module, exports, __webpack_require__) { 2355 | 2356 | 'use strict'; 2357 | 2358 | var NULL = null; 2359 | 2360 | /** 2361 | * 变换两个参数的函数到多个参数 2362 | * @param {Function} fn 基函数 2363 | * @return {Function} 变换后的函数 2364 | * @demo 2365 | * function add(x, y) { return x+y; } 2366 | * add = redo(add); 2367 | * add(1,2,3) => 6 2368 | */ 2369 | var redo = function redo(fn) { 2370 | return function () { 2371 | var args = arguments; 2372 | var ret = fn(args[0], args[1]); 2373 | for (var i = 2, l = args.length; i < l; i++) { 2374 | ret = fn(ret, args[i]); 2375 | } 2376 | return ret; 2377 | }; 2378 | }; 2379 | 2380 | var OBJECT = 'object'; 2381 | var isObject = function isObject(v) { 2382 | return typeof v === OBJECT; 2383 | }; 2384 | 2385 | var isWindow = function isWindow(v) { 2386 | return v !== NULL && v === v.window; 2387 | }; 2388 | 2389 | // 参考了zepto 2390 | var isPlainObject = function isPlainObject(v) { 2391 | return v !== NULL && isObject(v) && !isWindow(v) && Object.getPrototypeOf(v) === Object.prototype; 2392 | }; 2393 | 2394 | var isArray = Array.isArray; 2395 | if (false) { 2396 | if (!isArray) { 2397 | isArray = function (v) { 2398 | return toString.call(v) === ARRAY_TYPE; 2399 | }; 2400 | } 2401 | } 2402 | 2403 | /** 2404 | * 对象扩展 2405 | * @param {Object} receiver 2406 | * @param {Object} supplier 2407 | * @return {Object} 扩展后的receiver对象 2408 | * @note 这个extend方法是定制的, 不要拷贝到其他地方用!!! 2409 | */ 2410 | var extend = function extend() { 2411 | var receiver = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; 2412 | var supplier = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; 2413 | 2414 | for (var key in supplier) { 2415 | // `supplier`中不是未定义的键 都可以执行扩展 2416 | if (supplier.hasOwnProperty(key) && supplier[key] !== undefined) { 2417 | if (isArray(supplier[key])) { 2418 | receiver[key] = [].concat(supplier[key]); 2419 | } else if (isPlainObject(supplier[key])) { 2420 | receiver[key] = extend({}, supplier[key]); 2421 | } else { 2422 | receiver[key] = supplier[key]; 2423 | } 2424 | } 2425 | } 2426 | return receiver; 2427 | }; 2428 | 2429 | var noop = function noop() {}; 2430 | 2431 | module.exports = { 2432 | extend: redo(extend), 2433 | noop: noop, 2434 | isPlainObject: isPlainObject 2435 | }; 2436 | 2437 | /***/ } 2438 | /******/ ]) 2439 | }); 2440 | ; 2441 | 2442 | /***/ } 2443 | /******/ ]) 2444 | }); 2445 | ; -------------------------------------------------------------------------------- /dist/salt-fetch.min.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define("saltFetch",[],t):"object"==typeof exports?exports.saltFetch=t():e.saltFetch=t()}(this,function(){return function(e){function t(r){if(n[r])return n[r].exports;var o=n[r]={exports:{},id:r,loaded:!1};return e[r].call(o.exports,o,o.exports,t),o.loaded=!0,o.exports}var n={};return t.m=e,t.c=n,t.p="",t(0)}([function(e,t,n){var r=window.salt=window.salt||{},o=n(1),a=n(2);r.fetch=o,r.storage=a,e.exports={fetch:o,storage:a}},function(e,t,n){!function(t,r){e.exports=r(n(2))}(this,function(e){return function(e){function t(r){if(n[r])return n[r].exports;var o=n[r]={exports:{},id:r,loaded:!1};return e[r].call(o.exports,o,o.exports,t),o.loaded=!0,o.exports}var n={};return t.m=e,t.c=n,t.p="",t(0)}([function(e,t,n){"use strict";e.exports=n(1)},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var o=function(){function e(e,t){for(var n=0;n=200&&e.status<300||304===e.status){var r=e.responseText;switch(t.accept){case g:try{r=JSON.parse(r)}catch(n){console.warn("The response can NOT be parsed to JSON object.",r)}break;case h:(0,eval)(r);break;case v:r=e.responseXML}t.success(r,e)}else!e.__aborted&&t.error(e.status,e)};e.addEventListener("readystatechange",n);var r=function(){e.__finished||t.abort(e.status,e)};e.addEventListener("abort",r);var o=function(){e.__finished||(e.__finished=!0,t.complete(e.status,e),delete e.__aborted)};e.addEventListener("loadend",o)},w={url:"",mark:{},method:d,accept:"*",data:null,header:{},withCredentials:p,success:i,error:i,complete:i,abort:i,log:l,traditional:l},k=function(e){e=o({},w,e),c(e.url)&&(e.header={});var t=new XMLHttpRequest;b(t,e),t.open(e.method,a(e.url,o({},e.mark,e.method===d?e.data:{}),e.traditional)),t.withCredentials=s(e.withCredentials)?e.withCredentials:c(e.url),_(t,e),t.send(e.method===d?p:e.data!==p?u(e.data,e.traditional):p);var n=t.abort;return t.abort=function(){t.__aborted=!0,n.call(t)},t};k.fallback=!1,k.supportCORS=m,e.exports=k},function(e,t,n){"use strict";var r="undefined"!=typeof window,o=r?document:null,a=encodeURIComponent,i=null,c=Object.prototype.toString,s="[object Array]",u="[object Object]",l=!0,f=!l,p={dummy:l};p.then=p["catch"]=function(){return p};var d=r&&(!!window.ActiveXObject||"ActiveXObject"in window),h=function(e){return e},v=function(e){return function(){for(var t=arguments,n=e(t[0],t[1]),r=2,o=t.length;re._checkData.duration?x:e._checkData.until&&t>e._checkData.until?x:O}},{key:"set",value:function(e,t){var n=this,r=arguments.length,o=function(o,a){try{n._data||n._lazyInit(),1===r?y(e)?n._data=e:(n._data[S]=e,n._placeholderUsed=x):l(e,t,n._data),n._storage.set(n._DATA_KEY,n._data),o()}catch(i){a(i)}};return n.config.async?new Promise(o):void o(_,a)}},{key:"get",value:function(e){var t=this,n=void 0,r=function(r,o){try{t._data||t._lazyInit(),n=e?f(e,t._data):t._placeholderUsed?t._data[S]:t._data,r(n)}catch(a){o(a)}};return t.config.async?new Promise(r):(r(_,a),n)}},{key:"has",value:function(e){var t=this,n=void 0,r=function(r,o){try{if(t._data||t._lazyInit(),t._placeholderUsed||h(t._data))n=t._data.hasOwnProperty(S)?{has:!0,value:t._data[S]}:{},r(n);else{if(!e)throw new Error("a `path` argument should be passed into the `has` method");n=p(e,t._data)?{has:!0,value:f(e,t._data)}:{},r(n)}}catch(a){o(a)}};return t.config.async?new Promise(r):(r(_,a),n)}},{key:"remove",value:function(e){var t=this,n=function(n,r){try{t._data||t._lazyInit(),e?(d(e,t._data),t._storage.set(t._DATA_KEY,t._data)):t.set({}),n()}catch(o){r(o)}};return t.config.async?new Promise(n):void n(_,a)}},{key:"destroy",value:function(){var e=this;e._storage.remove(e._CHECK_KEY),e._storage.remove(e._DATA_KEY)}},{key:"dump",value:function(){JSON&&console&&console.log(JSON.stringify(this._data,k,4))}}]),e}(),N=function(e){return new A(e)};N.version=q,N._variable=T,N.support=E,N.setGlobal=function(e){P=m({},C,e)},N.getGlobal=function(e){return e?P[e]:P};var T={};e.exports=N},function(e,t,n){"use strict";var r=null,o=function(e){return function(){for(var t=arguments,n=e(t[0],t[1]),r=2,o=t.length;r 2 | 3 | 4 | 5 | 6 | 7 | 8 | salt.etch(Mobile ONLY Version) 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "salt-fetch", 3 | "version": "2.0.1", 4 | "description": "独立的`ajax/jsonp`模块。`Salt.fetch`底层由[NattyFetch](https://github.com/Jias/natty-fetch)实现。为了给`Salt`的使用者提供一致的开发体验,故将`NattyFetch`工具以`fetch`属性的方式集成在`Salt`命名空间下。", 5 | "main": "dist/salt-fetch.js", 6 | "files": [ 7 | "dist", 8 | "src", 9 | "README.md" 10 | ], 11 | "scripts": { 12 | "build": "gulp delete-dist-dir && gulp pack && gulp min" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/saltjs/salt-fetch.git" 17 | }, 18 | "author": "jias", 19 | "bugs": { 20 | "url": "https://github.com/saltjs/salt-fetch/issues" 21 | }, 22 | "homepage": "https://github.com/saltjs/salt-fetch#readme", 23 | "license": "MIT", 24 | "dependencies": { 25 | "natty-fetch": "^2.0.0", 26 | "natty-storage": "^1.0.0" 27 | }, 28 | "devDependencies": { 29 | "del": "^2.0.1", 30 | "gulp": "^3.9.0", 31 | "gulp-rename": "^1.2.2", 32 | "gulp-sourcemaps": "^1.5.2", 33 | "gulp-uglify": "^1.4.0", 34 | "lie": "^3.0.2", 35 | "pump": "^1.0.1", 36 | "webpack": "^1.13.1", 37 | "webpack-stream": "^2.1.0" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | var salt = window.salt = window.salt || {}; 2 | var nattyFetch = require('natty-fetch'); 3 | var nattyStorage = require('natty-storage'); 4 | salt.fetch = nattyFetch; 5 | salt.storage = nattyStorage; 6 | 7 | module.exports = { 8 | fetch: nattyFetch, 9 | storage: nattyStorage 10 | }; --------------------------------------------------------------------------------