1 | var qs = require('./querystring.js')
|
2 |
|
3 | var METHODS = ['put', 'head', 'patch', 'delete', 'post', 'get']
|
4 | var CONTENT_TYPE = 'Content-Type'
|
5 | var CONTENT_TYPE_FORM = 'application/x-www-form-urlencoded'
|
6 | var CONTENT_TYPE_JSON = 'application/json; charset=utf-8'
|
7 |
|
8 | function combineUrl (base, newurl) {
|
9 | base = norm(base)
|
10 | newurl = norm(newurl)
|
11 |
|
12 | if (!newurl || !base) return base + newurl
|
13 |
|
14 | if (newurl.startsWith('http://') || newurl.startsWith('https://') || newurl.startsWith('//')) {
|
15 | return newurl
|
16 | }
|
17 |
|
18 | if (!base.endsWith('/')) base += '/'
|
19 | if (newurl.startsWith('/')) newurl = newurl.substring(1)
|
20 | return base + newurl
|
21 | }
|
22 |
|
23 | function merge (req, obj) {
|
24 | return Object.assign(req.clone(), obj)
|
25 | }
|
26 |
|
27 | function newRequest () {
|
28 | var r = {
|
29 | parser: '',
|
30 | beforehooks: [],
|
31 | afterhooks: [],
|
32 | baseurl: '',
|
33 | query: {},
|
34 | meta: {},
|
35 | }
|
36 |
|
37 | r.clone = function () {
|
38 | return Object.assign({}, this, {
|
39 | query: Object.assign({}, this.query),
|
40 | meta: Object.assign({}, this.meta),
|
41 | })
|
42 | }
|
43 |
|
44 | r.addQuery = function (key, val) {
|
45 | var req = this.clone()
|
46 | req.query[key] = val
|
47 | return req
|
48 | }
|
49 |
|
50 | r.removeQuery = function (key) {
|
51 | var req = this.clone()
|
52 | if (req.query[key] !== undefined) req.query[key] = undefined
|
53 | return req
|
54 | }
|
55 |
|
56 | r.setQuery = function (query) {
|
57 | return merge(this, { query: query })
|
58 | }
|
59 |
|
60 | r.clearHooks = function () {
|
61 | return merge(this, { beforehooks: [], afterhooks: [] })
|
62 | }
|
63 |
|
64 | r.beforeHook = function (cb) {
|
65 | var beforehooks = this.beforehooks.slice()
|
66 | beforehooks.push(cb)
|
67 | return merge(this, { beforehooks: beforehooks })
|
68 | }
|
69 |
|
70 | r.afterHook = function (cb) {
|
71 | var afterhooks = this.afterhooks.slice()
|
72 | afterhooks.push(cb)
|
73 | return merge(this, { afterhooks: afterhooks })
|
74 | }
|
75 |
|
76 | r.setHeader = function (headers) {
|
77 | headers = Object.assign({}, this.headers, headers)
|
78 | headers[CONTENT_TYPE] = undefined
|
79 | return merge(this, { headers: headers })
|
80 | }
|
81 |
|
82 | METHODS.map(function (method) {
|
83 | r[method] = function (url, data, cb) {
|
84 | return send(merge(this, { method: method, baseurl: combineUrl(this.baseurl, url) }), data, cb)
|
85 | }
|
86 | })
|
87 |
|
88 |
|
89 | r.setBaseUrl = function (url) {
|
90 | return merge(this, { baseurl: url })
|
91 | }
|
92 |
|
93 | r.contentTypeJson = function () {
|
94 | return merge(this, { content_type: CONTENT_TYPE_JSON })
|
95 | }
|
96 |
|
97 | r.contentTypeForm = function () {
|
98 | return merge(this, { content_type: CONTENT_TYPE_FORM })
|
99 | }
|
100 |
|
101 | r.setContentType = function (ty) {
|
102 | return merge(this, { content_type: norm(ty) })
|
103 | }
|
104 |
|
105 | r.setParser = function (parser) {
|
106 | return merge(this, { parser: norm(parser) })
|
107 | }
|
108 |
|
109 | r.setBody = function (body) {
|
110 | return merge(this, { body: body })
|
111 | }
|
112 |
|
113 | r.setMeta = function (k, v) {
|
114 | var req = this.clone()
|
115 | req.meta[k] = v
|
116 | return req
|
117 | }
|
118 |
|
119 | return r
|
120 | }
|
121 |
|
122 | function send (req, data, cb) {
|
123 | cb = cb || function () {}
|
124 | if (isFunc(data)) {
|
125 | cb = data
|
126 | data = undefined
|
127 | }
|
128 |
|
129 | var rs
|
130 | var promise = new Promise(function (resolve) {
|
131 | rs = function (res) {
|
132 | try {
|
133 | cb(res.error, res.body, res.code)
|
134 | } catch (_) {}
|
135 | resolve(res)
|
136 | }
|
137 | })
|
138 |
|
139 | if (data) {
|
140 | req = req.clone()
|
141 | req.body = data
|
142 | if (req.content_type === CONTENT_TYPE_JSON) req.body = env.Jsonify(data)
|
143 | if (req.content_type === CONTENT_TYPE_FORM) {
|
144 | req.body = qs.stringify(data)
|
145 | }
|
146 | }
|
147 |
|
148 | waterfall(req.beforehooks.slice(), { request: req }, function (bp) {
|
149 | if (bp.error) return rs({ body: undefined, code: 0, error: bp.error })
|
150 | dosend(bp.request, function (err, body, code) {
|
151 | waterfall(req.afterhooks.slice(), { request: req, code: code, body: body, err: err }, function (param) {
|
152 | var body = param.body
|
153 |
|
154 | if (req.parser == 'json' && param.body) {
|
155 | try {
|
156 | body = env.ParseJson(param.body)
|
157 | } catch (e) {
|
158 | param.err = param.err || 'invalid json'
|
159 | }
|
160 | }
|
161 | var err = param.err
|
162 | if (code !== 200) err = 'not 200'
|
163 | rs({ body: body, code: param.code, error: err })
|
164 | })
|
165 | })
|
166 | })
|
167 | return promise
|
168 | }
|
169 |
|
170 | var dosend = function (req, cb) {
|
171 | var q = qs.stringify(req.query)
|
172 | if (q) q = '?' + q
|
173 |
|
174 | var request = new env.XMLHttpRequest()
|
175 | request.onreadystatechange = function (e) {
|
176 | if (request.readyState !== 4) return
|
177 | if (request.status === 0) {
|
178 | cb('network_error', request.responseText, request.status)
|
179 | } else {
|
180 | cb(undefined, request.responseText, request.status)
|
181 | }
|
182 | }
|
183 |
|
184 | request.open(req.method, req.baseurl + q)
|
185 | for (var i in req.headers) request.setRequestHeader(i, req.headers[i])
|
186 | if (req.content_type) {
|
187 | request.setRequestHeader(CONTENT_TYPE, req.content_type)
|
188 | }
|
189 | request.send(req.body)
|
190 | }
|
191 |
|
192 | function norm (str) {
|
193 | return (str || '').trim()
|
194 | }
|
195 |
|
196 | function isFunc (f) {
|
197 | return f && {}.toString.call(f) === '[object Function]'
|
198 | }
|
199 |
|
200 | function waterfall (ps, param, cb) {
|
201 | if (!ps || ps.length === 0) return cb(param)
|
202 |
|
203 | var fp = ps.shift()
|
204 | if (!isFunc(fp)) return waterfall(ps, param, cb)
|
205 | fp(param, function (out) {
|
206 | return out === false ? cb(param) : waterfall(ps, param, cb)
|
207 | })
|
208 | }
|
209 |
|
210 | var ajax = newRequest()
|
211 | var env = { XMLHttpRequest: {}, Jsonify: JSON.stringify, ParseJson: JSON.parse }
|
212 | ajax.env = env
|
213 | ajax.waterfall = waterfall
|
214 | module.exports = ajax
|