├── .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 |