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