From 9ec66c881fad1dee4c972bd9a119db458bb6ea35 Mon Sep 17 00:00:00 2001 From: fxy060608 Date: Tue, 6 Apr 2021 20:51:41 +0800 Subject: [PATCH] feat: defineTaskApi --- packages/shims-uni-app.d.ts | 40 +++ packages/uni-api/src/helpers/api/index.ts | 69 +++-- packages/uni-api/src/index.ts | 3 + .../uni-api/src/protocols/network/request.js | 113 -------- .../uni-api/src/protocols/network/request.ts | 125 +++++++++ packages/uni-h5/dist/uni-h5.esm.js | 252 +++++++++++++++++- packages/uni-h5/src/service/api/index.ts | 2 + .../uni-h5/src/service/api/network/request.ts | 158 +++++++++++ packages/uni-mp-alipay/dist/uni.api.esm.js | 22 +- packages/uni-mp-baidu/dist/uni.api.esm.js | 22 +- packages/uni-mp-qq/dist/uni.api.esm.js | 22 +- packages/uni-mp-toutiao/dist/uni.api.esm.js | 22 +- packages/uni-mp-weixin/dist/uni.api.esm.js | 22 +- .../uni-quickapp-webview/dist/uni.api.esm.js | 22 +- 14 files changed, 702 insertions(+), 192 deletions(-) delete mode 100644 packages/uni-api/src/protocols/network/request.js create mode 100644 packages/uni-api/src/protocols/network/request.ts create mode 100644 packages/uni-h5/src/service/api/network/request.ts diff --git a/packages/shims-uni-app.d.ts b/packages/shims-uni-app.d.ts index c42dbfd54..ddd12b7e5 100644 --- a/packages/shims-uni-app.d.ts +++ b/packages/shims-uni-app.d.ts @@ -34,6 +34,12 @@ declare namespace UniApp { topWindow?: LayoutWindowOptions leftWindow?: LayoutWindowOptions rightWindow?: LayoutWindowOptions + networkTimeout: { + connectSocket: number + downloadFile: number + request: number + uploadFile: number + } } interface UniRoute { @@ -202,6 +208,40 @@ declare namespace UniApp { name: string, res: Parameters[0]>[0] ): void + /** + * 订阅 View 的自定义事件,回调函数会接收所有传入事件触发函数的额外参数。 + * @param event + * @param callback + */ + subscribe(event: string, callback: Function): void + /** + * 取消订阅 View 的自定义事件 + * 如果没有提供参数,则移除所有的事件监听器; + * 如果只提供了事件,则移除该事件所有的监听器; + * 如果同时提供了事件与回调,则只移除这个回调的监听器。 + * @param event + * @param callback + */ + unsubscribe(event: string, callback?: Function): void + /** + * 执行 Service 层 API 回调 + * @param callbackId + * @param args + */ + invokeCallbackHandler(callbackId: number, args: unknown): void + /** + * 向 View 层发送事件 + * @param event + * @param args + * @param pageId + */ publishHandler(event: string, args: unknown, pageId: number): void + /** + * 接收 View 层事件(通常由 View 层调用,并暴露至全局 UniServiceJSBridge 对象中) + * @param event + * @param args + * @param pageId + */ + subscribeHandler(event: string, args: unknown, pageId: number): void } } diff --git a/packages/uni-api/src/helpers/api/index.ts b/packages/uni-api/src/helpers/api/index.ts index 208551c7a..a859ab6e5 100644 --- a/packages/uni-api/src/helpers/api/index.ts +++ b/packages/uni-api/src/helpers/api/index.ts @@ -65,9 +65,22 @@ function wrapperOffApi(name: string, fn: Function) { } } +function invokeSuccess(id: number, name: string, res: unknown) { + return invokeCallback(id, extend(res || {}, { errMsg: name + ':ok' })) +} + +function invokeFail(id: number, name: string, err: string) { + return invokeCallback(id, { errMsg: name + ':fail' + (err ? ' ' + err : '') }) +} + function wrapperTaskApi(name: string, fn: Function, options?: ApiOptions) { - return (args: Record) => - fn.apply(null, [args, createAsyncApiCallback(name, args, options)]) + return (args: Record) => { + const id = createAsyncApiCallback(name, args, options) + return fn(args, { + resolve: (res: unknown) => invokeSuccess(id, name, res), + reject: (err: string) => invokeFail(id, name, err), + }) + } } function wrapperSyncApi(fn: Function) { @@ -82,22 +95,18 @@ function wrapperAsyncApi( return (args: Record) => { const id = createAsyncApiCallback(name, args, options) fn(args) - .then((res) => { - invokeCallback(id, extend(res || {}, { errMsg: name + ':ok' })) - }) - .catch((err) => { - invokeCallback(id, { errMsg: name + ':fail' + (err ? ' ' + err : '') }) - }) + .then((res) => invokeSuccess(id, name, res)) + .catch((err) => invokeFail(id, name, err)) } } -function wrapperApi( +function wrapperApi( fn: Function, name?: string, protocol?: ApiProtocols, options?: ApiOptions ) { - return (function (...args: any[]) { + return function (...args: any[]) { if (__DEV__) { const errMsg = validateProtocols(name!, args, protocol) if (errMsg) { @@ -105,50 +114,54 @@ function wrapperApi( } } return fn.apply(null, formatApiArgs(args, options)) - } as unknown) as T + } } export function defineOnApi( name: string, - fn: T, + fn: () => void, options?: ApiOptions ) { - return defineApi( + return (defineApi( API_TYPE_ON, name, fn, __DEV__ ? API_TYPE_ON_PROTOCOLS : undefined, options - ) as T + ) as unknown) as T } export function defineOffApi( name: string, - fn: T, + fn: () => void, options?: ApiOptions ) { - return defineApi( + return (defineApi( API_TYPE_OFF, name, fn, __DEV__ ? API_TYPE_ON_PROTOCOLS : undefined, options - ) as T + ) as unknown) as T } -export function defineTaskApi( +type TaskApiLike = (args: any) => any + +export function defineTaskApi>( name: string, - fn: T, + fn: ( + args: Omit, + res: { + resolve: (res: AsyncApiRes

) => void + reject: (err?: string) => void + } + ) => ReturnType, protocol?: ApiProtocols, options?: ApiOptions ) { - return defineApi( - API_TYPE_TASK, - name, - fn, - __DEV__ ? protocol : undefined, - options - ) as T + return (promisify( + defineApi(API_TYPE_TASK, name, fn, __DEV__ ? protocol : undefined, options) + ) as unknown) as T } export function defineSyncApi( @@ -157,13 +170,13 @@ export function defineSyncApi( protocol?: ApiProtocols, options?: ApiOptions ) { - return defineApi( + return (defineApi( API_TYPE_SYNC, name, fn, __DEV__ ? protocol : undefined, options - ) as T + ) as unknown) as T } interface AsyncMethodOptionLike { success?: (...args: any[]) => void diff --git a/packages/uni-api/src/index.ts b/packages/uni-api/src/index.ts index 8ec6ee834..a0d5141d1 100644 --- a/packages/uni-api/src/index.ts +++ b/packages/uni-api/src/index.ts @@ -20,6 +20,9 @@ export * from './protocols/location/openLocation' export * from './protocols/media/chooseImage' export * from './protocols/media/chooseVideo' export * from './protocols/media/getImageInfo' + +export * from './protocols/network/request' + export * from './protocols/route/route' // helpers diff --git a/packages/uni-api/src/protocols/network/request.js b/packages/uni-api/src/protocols/network/request.js deleted file mode 100644 index 60a92544b..000000000 --- a/packages/uni-api/src/protocols/network/request.js +++ /dev/null @@ -1,113 +0,0 @@ -import { hasOwn, isPlainObject } from 'uni-shared' - -const method = { - OPTIONS: 'OPTIONS', - GET: 'GET', - HEAD: 'HEAD', - POST: 'POST', - PUT: 'PUT', - DELETE: 'DELETE', - TRACE: 'TRACE', - CONNECT: 'CONNECT' -} -const dataType = { - JSON: 'json' -} -const responseType = { - TEXT: 'text', - ARRAYBUFFER: 'arraybuffer' -} - -const encode = encodeURIComponent - -function stringifyQuery(url, data) { - let str = url.split('#') - const hash = str[1] || '' - str = str[0].split('?') - let query = str[1] || '' - url = str[0] - const search = query.split('&').filter(item => item) - query = {} - search.forEach(item => { - item = item.split('=') - query[item[0]] = item[1] - }) - for (const key in data) { - if (hasOwn(data, key)) { - let v = data[key] - if (typeof v === 'undefined' || v === null) { - v = '' - } else if (isPlainObject(v)) { - v = JSON.stringify(v) - } - query[encode(key)] = encode(v) - } - } - query = Object.keys(query) - .map(item => `${item}=${query[item]}`) - .join('&') - return url + (query ? '?' + query : '') + (hash ? '#' + hash : '') -} - -export const request = { - method: { - type: String, - validator(value, params) { - value = (value || '').toUpperCase() - params.method = - Object.values(method).indexOf(value) < 0 ? method.GET : value - } - }, - data: { - type: [Object, String, Array, ArrayBuffer], - validator(value, params) { - params.data = value || '' - } - }, - url: { - type: String, - required: true, - validator(value, params) { - if ( - params.method === method.GET && - isPlainObject(params.data) && - Object.keys(params.data).length - ) { - // 将 method,data 校验提前,保证 url 校验时,method,data 已被格式化 - params.url = stringifyQuery(value, params.data) - } - } - }, - header: { - type: Object, - validator(value, params) { - const header = (params.header = value || {}) - if (params.method !== method.GET) { - if ( - !Object.keys(header).find(key => key.toLowerCase() === 'content-type') - ) { - header['Content-Type'] = 'application/json' - } - } - } - }, - dataType: { - type: String, - validator(value, params) { - params.dataType = (value || dataType.JSON).toLowerCase() - } - }, - responseType: { - type: String, - validator(value, params) { - value = (value || '').toLowerCase() - params.responseType = - Object.values(responseType).indexOf(value) < 0 - ? responseType.TEXT - : value - } - }, - withCredentials: { - type: Boolean - } -} diff --git a/packages/uni-api/src/protocols/network/request.ts b/packages/uni-api/src/protocols/network/request.ts new file mode 100644 index 000000000..e71aa64ad --- /dev/null +++ b/packages/uni-api/src/protocols/network/request.ts @@ -0,0 +1,125 @@ +import { hasOwn, isPlainObject } from '@vue/shared' +import { ApiOptions, ApiProtocol } from '../type' + +export const API_REQUEST = 'request' + +const METHOD = [ + 'GET', + 'OPTIONS', + 'HEAD', + 'POST', + 'PUT', + 'DELETE', + 'TRACE', + 'CONNECT', +] + +const DEFAULT_METHOD = 'GET' + +const dataType = { + JSON: 'json', +} + +const RESPONSE_TYPE = ['text', 'arraybuffer'] +const DEFAULT_RESPONSE_TYPE = 'text' + +const encode = encodeURIComponent + +function stringifyQuery(url: string, data: Record) { + let str = url.split('#') + const hash = str[1] || '' + str = str[0].split('?') + let query = str[1] || '' + url = str[0] + const search = query.split('&').filter((item) => item) + const params: Record = {} + search.forEach((item) => { + const part = item.split('=') + params[part[0]] = part[1] + }) + for (const key in data) { + if (hasOwn(data, key)) { + let v = data[key] + if (typeof v === 'undefined' || v === null) { + v = '' + } else if (isPlainObject(v)) { + v = JSON.stringify(v) + } + params[encode(key)] = encode(v) + } + } + query = Object.keys(params) + .map((item) => `${item}=${params[item]}`) + .join('&') + return url + (query ? '?' + query : '') + (hash ? '#' + hash : '') +} + +export const RequestProtocol: ApiProtocol = { + method: { + type: String, + }, + data: { + type: [Object, String, Array, ArrayBuffer], + }, + url: { + type: String, + required: true, + }, + header: { + type: Object, + }, + dataType: { + type: String, + }, + responseType: { + type: String, + }, + withCredentials: { + type: Boolean, + }, +} + +export const RequestOptions: ApiOptions = { + formatArgs: { + method(value, params) { + params.method = (value || '').toUpperCase() + if (METHOD.indexOf(params.method) === -1) { + params.method = DEFAULT_METHOD + } + }, + data(value, params) { + params.data = value || '' + }, + url(value, params) { + if ( + params.method === DEFAULT_METHOD && + isPlainObject(params.data) && + Object.keys(params.data).length + ) { + // 将 method,data 校验提前,保证 url 校验时,method,data 已被格式化 + params.url = stringifyQuery(value, params.data) + } + }, + header(value, params) { + const header = (params.header = value || {}) + if (params.method !== DEFAULT_METHOD) { + if ( + !Object.keys(header).find( + (key) => key.toLowerCase() === 'content-type' + ) + ) { + header['Content-Type'] = 'application/json' + } + } + }, + dataType(value, params) { + params.dataType = (value || dataType.JSON).toLowerCase() + }, + responseType(value, params) { + params.responseType = (value || '').toLowerCase() + if (RESPONSE_TYPE.indexOf(params.responseType) === -1) { + params.responseType = DEFAULT_RESPONSE_TYPE + } + }, + }, +} diff --git a/packages/uni-h5/dist/uni-h5.esm.js b/packages/uni-h5/dist/uni-h5.esm.js index 13816b1ec..613084e61 100644 --- a/packages/uni-h5/dist/uni-h5.esm.js +++ b/packages/uni-h5/dist/uni-h5.esm.js @@ -7483,7 +7483,7 @@ var lookup = new Uint8Array(256); for (var i = 0; i < chars.length; i++) { lookup[chars.charCodeAt(i)] = i; } -function encode(arraybuffer) { +function encode$1(arraybuffer) { var bytes = new Uint8Array(arraybuffer), i, len = bytes.length, base64 = ""; for (i = 0; i < len; i += 3) { base64 += chars[bytes[i] >> 2]; @@ -7816,8 +7816,20 @@ function wrapperOffApi(name, fn) { } }; } +function invokeSuccess(id2, name, res) { + return invokeCallback(id2, extend(res || {}, {errMsg: name + ":ok"})); +} +function invokeFail(id2, name, err) { + return invokeCallback(id2, {errMsg: name + ":fail" + (err ? " " + err : "")}); +} function wrapperTaskApi(name, fn, options) { - return (args) => fn.apply(null, [args, createAsyncApiCallback(name, args, options)]); + return (args) => { + const id2 = createAsyncApiCallback(name, args, options); + return fn(args, { + resolve: (res) => invokeSuccess(id2, name, res), + reject: (err) => invokeFail(id2, name, err) + }); + }; } function wrapperSyncApi(fn) { return (...args) => fn.apply(null, args); @@ -7825,11 +7837,7 @@ function wrapperSyncApi(fn) { function wrapperAsyncApi(name, fn, options) { return (args) => { const id2 = createAsyncApiCallback(name, args, options); - fn(args).then((res) => { - invokeCallback(id2, extend(res || {}, {errMsg: name + ":ok"})); - }).catch((err) => { - invokeCallback(id2, {errMsg: name + ":fail" + (err ? " " + err : "")}); - }); + fn(args).then((res) => invokeSuccess(id2, name, res)).catch((err) => invokeFail(id2, name, err)); }; } function wrapperApi(fn, name, protocol, options) { @@ -7849,6 +7857,9 @@ function defineOnApi(name, fn, options) { function defineOffApi(name, fn, options) { return defineApi(API_TYPE_OFF, name, fn, process.env.NODE_ENV !== "production" ? API_TYPE_ON_PROTOCOLS : void 0, options); } +function defineTaskApi(name, fn, protocol, options) { + return promisify(defineApi(API_TYPE_TASK, name, fn, process.env.NODE_ENV !== "production" ? protocol : void 0, options)); +} function defineSyncApi(name, fn, protocol, options) { return defineApi(API_TYPE_SYNC, name, fn, process.env.NODE_ENV !== "production" ? protocol : void 0, options); } @@ -7889,7 +7900,7 @@ const base64ToArrayBuffer = defineSyncApi(API_BASE64_TO_ARRAY_BUFFER, (base64) = return decode(base64); }, Base64ToArrayBufferProtocol); const arrayBufferToBase64 = defineSyncApi(API_ARRAY_BUFFER_TO_BASE64, (arrayBuffer) => { - return encode(arrayBuffer); + return encode$1(arrayBuffer); }, ArrayBufferToBase64Protocol); const API_UPX2PX = "upx2px"; const Upx2pxProtocol = [ @@ -8139,6 +8150,109 @@ const GetImageInfoProtocol = { required: true } }; +const API_REQUEST = "request"; +const METHOD = [ + "GET", + "OPTIONS", + "HEAD", + "POST", + "PUT", + "DELETE", + "TRACE", + "CONNECT" +]; +const DEFAULT_METHOD = "GET"; +const dataType = { + JSON: "json" +}; +const RESPONSE_TYPE = ["text", "arraybuffer"]; +const DEFAULT_RESPONSE_TYPE = "text"; +const encode = encodeURIComponent; +function stringifyQuery(url, data) { + let str = url.split("#"); + const hash = str[1] || ""; + str = str[0].split("?"); + let query = str[1] || ""; + url = str[0]; + const search = query.split("&").filter((item) => item); + const params = {}; + search.forEach((item) => { + const part = item.split("="); + params[part[0]] = part[1]; + }); + for (const key in data) { + if (hasOwn$1(data, key)) { + let v2 = data[key]; + if (typeof v2 === "undefined" || v2 === null) { + v2 = ""; + } else if (isPlainObject(v2)) { + v2 = JSON.stringify(v2); + } + params[encode(key)] = encode(v2); + } + } + query = Object.keys(params).map((item) => `${item}=${params[item]}`).join("&"); + return url + (query ? "?" + query : "") + (hash ? "#" + hash : ""); +} +const RequestProtocol = { + method: { + type: String + }, + data: { + type: [Object, String, Array, ArrayBuffer] + }, + url: { + type: String, + required: true + }, + header: { + type: Object + }, + dataType: { + type: String + }, + responseType: { + type: String + }, + withCredentials: { + type: Boolean + } +}; +const RequestOptions = { + formatArgs: { + method(value, params) { + params.method = (value || "").toUpperCase(); + if (METHOD.indexOf(params.method) === -1) { + params.method = DEFAULT_METHOD; + } + }, + data(value, params) { + params.data = value || ""; + }, + url(value, params) { + if (params.method === DEFAULT_METHOD && isPlainObject(params.data) && Object.keys(params.data).length) { + params.url = stringifyQuery(value, params.data); + } + }, + header(value, params) { + const header = params.header = value || {}; + if (params.method !== DEFAULT_METHOD) { + if (!Object.keys(header).find((key) => key.toLowerCase() === "content-type")) { + header["Content-Type"] = "application/json"; + } + } + }, + dataType(value, params) { + params.dataType = (value || dataType.JSON).toLowerCase(); + }, + responseType(value, params) { + params.responseType = (value || "").toLowerCase(); + if (RESPONSE_TYPE.indexOf(params.responseType) === -1) { + params.responseType = DEFAULT_RESPONSE_TYPE; + } + } + } +}; function encodeQueryString(url) { if (typeof url !== "string") { return url; @@ -8480,6 +8594,125 @@ const getImageInfo = defineAsyncApi(API_GET_IMAGE_INFO, ({src}) => { img.src = src; }); }, GetImageInfoProtocol, GetImageInfoOptions); +const request = defineTaskApi(API_REQUEST, ({ + url, + data, + header, + method, + dataType: dataType2, + responseType, + withCredentials, + timeout = __uniConfig.networkTimeout.request +}, {resolve, reject}) => { + let body = null; + const contentType = normalizeContentType(header); + if (method !== "GET") { + if (typeof data === "string" || data instanceof ArrayBuffer) { + body = data; + } else { + if (contentType === "json") { + try { + body = JSON.stringify(data); + } catch (error) { + body = data.toString(); + } + } else if (contentType === "urlencoded") { + const bodyArray = []; + for (const key in data) { + if (hasOwn$1(data, key)) { + bodyArray.push(encodeURIComponent(key) + "=" + encodeURIComponent(data[key])); + } + } + body = bodyArray.join("&"); + } else { + body = data.toString(); + } + } + } + const xhr = new XMLHttpRequest(); + const requestTask = new RequestTask(xhr); + xhr.open(method, url); + for (const key in header) { + if (hasOwn$1(header, key)) { + xhr.setRequestHeader(key, header[key]); + } + } + const timer = setTimeout(function() { + xhr.onload = xhr.onabort = xhr.onerror = null; + requestTask.abort(); + reject("timeout"); + }, timeout); + xhr.responseType = responseType; + xhr.onload = function() { + clearTimeout(timer); + const statusCode = xhr.status; + let res = responseType === "text" ? xhr.responseText : xhr.response; + if (responseType === "text" && dataType2 === "json") { + try { + res = JSON.parse(res); + } catch (error) { + } + } + resolve({ + data: res, + statusCode, + header: parseHeaders(xhr.getAllResponseHeaders()), + cookies: [] + }); + }; + xhr.onabort = function() { + clearTimeout(timer); + reject("abort"); + }; + xhr.onerror = function() { + clearTimeout(timer); + reject(); + }; + xhr.withCredentials = withCredentials; + xhr.send(body); + return requestTask; +}, RequestProtocol, RequestOptions); +function normalizeContentType(header) { + const name = Object.keys(header).find((name2) => name2.toLowerCase() === "content-type"); + if (!name) { + return; + } + const contentType = header[name]; + if (contentType.indexOf("application/json") === 0) { + return "json"; + } else if (contentType.indexOf("application/x-www-form-urlencoded") === 0) { + return "urlencoded"; + } + return "string"; +} +class RequestTask { + constructor(xhr) { + this._xhr = xhr; + } + abort() { + if (this._xhr) { + this._xhr.abort(); + delete this._xhr; + } + } + onHeadersReceived(callback) { + throw new Error("Method not implemented."); + } + offHeadersReceived(callback) { + throw new Error("Method not implemented."); + } +} +function parseHeaders(headers) { + const headersObject = {}; + headers.split("\n").forEach((header) => { + const find = header.match(/(\S+\s*):\s*(.*)/); + if (!find || find.length !== 3) { + return; + } + headersObject[find[1]] = find[2]; + }); + return headersObject; +} const navigateBack = defineAsyncApi(API_NAVIGATE_BACK, ({delta}) => new Promise((resolve, reject) => { let canBack = true; const vm = getCurrentPageVm(); @@ -8531,6 +8764,7 @@ var api = /* @__PURE__ */ Object.freeze({ getNetworkType, openDocument, getImageInfo, + request, navigateBack, navigateTo, redirectTo, @@ -9644,4 +9878,4 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) { ]); } _sfc_main.render = _sfc_render; -export {_sfc_main$1 as AsyncErrorComponent, _sfc_main as AsyncLoadingComponent, _sfc_main$o as Audio, _sfc_main$n as Canvas, _sfc_main$m as Checkbox, _sfc_main$l as CheckboxGroup, _sfc_main$k as Editor, _sfc_main$j as Form, index$2 as Icon, _sfc_main$h as Image, _sfc_main$g as Input, _sfc_main$f as Label, _sfc_main$e as MovableView, _sfc_main$d as Navigator, index as PageComponent, _sfc_main$c as Progress, _sfc_main$b as Radio, _sfc_main$a as RadioGroup, _sfc_main$i as ResizeSensor, _sfc_main$9 as RichText, _sfc_main$8 as ScrollView, _sfc_main$7 as Slider, _sfc_main$6 as SwiperItem, _sfc_main$5 as Switch, index$1 as Text, _sfc_main$4 as Textarea, UniServiceJSBridge$1 as UniServiceJSBridge, UniViewJSBridge$1 as UniViewJSBridge, _sfc_main$3 as View, addInterceptor, arrayBufferToBase64, base64ToArrayBuffer, canIUse, createIntersectionObserver, createSelectorQuery, getApp$1 as getApp, getCurrentPages$1 as getCurrentPages, getImageInfo, getNetworkType, getSystemInfo, getSystemInfoSync, makePhoneCall, navigateBack, navigateTo, offNetworkStatusChange, onNetworkStatusChange, openDocument, index$3 as plugin, promiseInterceptor, reLaunch, redirectTo, removeInterceptor, switchTab, uni$1 as uni, upx2px}; +export {_sfc_main$1 as AsyncErrorComponent, _sfc_main as AsyncLoadingComponent, _sfc_main$o as Audio, _sfc_main$n as Canvas, _sfc_main$m as Checkbox, _sfc_main$l as CheckboxGroup, _sfc_main$k as Editor, _sfc_main$j as Form, index$2 as Icon, _sfc_main$h as Image, _sfc_main$g as Input, _sfc_main$f as Label, _sfc_main$e as MovableView, _sfc_main$d as Navigator, index as PageComponent, _sfc_main$c as Progress, _sfc_main$b as Radio, _sfc_main$a as RadioGroup, _sfc_main$i as ResizeSensor, _sfc_main$9 as RichText, _sfc_main$8 as ScrollView, _sfc_main$7 as Slider, _sfc_main$6 as SwiperItem, _sfc_main$5 as Switch, index$1 as Text, _sfc_main$4 as Textarea, UniServiceJSBridge$1 as UniServiceJSBridge, UniViewJSBridge$1 as UniViewJSBridge, _sfc_main$3 as View, addInterceptor, arrayBufferToBase64, base64ToArrayBuffer, canIUse, createIntersectionObserver, createSelectorQuery, getApp$1 as getApp, getCurrentPages$1 as getCurrentPages, getImageInfo, getNetworkType, getSystemInfo, getSystemInfoSync, makePhoneCall, navigateBack, navigateTo, offNetworkStatusChange, onNetworkStatusChange, openDocument, index$3 as plugin, promiseInterceptor, reLaunch, redirectTo, removeInterceptor, request, switchTab, uni$1 as uni, upx2px}; diff --git a/packages/uni-h5/src/service/api/index.ts b/packages/uni-h5/src/service/api/index.ts index 8adba9389..83df40574 100644 --- a/packages/uni-h5/src/service/api/index.ts +++ b/packages/uni-h5/src/service/api/index.ts @@ -9,6 +9,8 @@ export * from './file/openDocument' export * from './media/getImageInfo' +export * from './network/request' + export * from './route/navigateBack' export * from './route/navigateTo' export * from './route/redirectTo' diff --git a/packages/uni-h5/src/service/api/network/request.ts b/packages/uni-h5/src/service/api/network/request.ts new file mode 100644 index 000000000..19515272e --- /dev/null +++ b/packages/uni-h5/src/service/api/network/request.ts @@ -0,0 +1,158 @@ +import { + API_REQUEST, + defineTaskApi, + RequestOptions, + RequestProtocol, +} from '@dcloudio/uni-api' +import { hasOwn } from '@vue/shared' + +export const request = defineTaskApi( + API_REQUEST, + ( + { + url, + data, + header, + method, + dataType, + responseType, + withCredentials, + timeout = __uniConfig.networkTimeout.request, + }, + { resolve, reject } + ) => { + let body = null + // 根据请求类型处理数据 + const contentType = normalizeContentType(header) + if (method !== 'GET') { + if (typeof data === 'string' || data instanceof ArrayBuffer) { + body = data + } else { + if (contentType === 'json') { + try { + body = JSON.stringify(data) + } catch (error) { + body = data!.toString() + } + } else if (contentType === 'urlencoded') { + const bodyArray = [] + for (const key in data) { + if (hasOwn(data, key)) { + bodyArray.push( + encodeURIComponent(key) + '=' + encodeURIComponent(data[key]) + ) + } + } + body = bodyArray.join('&') + } else { + body = data!.toString() + } + } + } + const xhr = new XMLHttpRequest() + const requestTask = new RequestTask(xhr) + xhr.open(method!, url) + for (const key in header) { + if (hasOwn(header, key)) { + xhr.setRequestHeader(key, header[key]) + } + } + + const timer = setTimeout(function () { + xhr.onload = xhr.onabort = xhr.onerror = null + requestTask.abort() + reject('timeout') + }, timeout) + xhr.responseType = responseType as 'arraybuffer' | 'text' + xhr.onload = function () { + clearTimeout(timer) + const statusCode = xhr.status + let res = responseType === 'text' ? xhr.responseText : xhr.response + if (responseType === 'text' && dataType === 'json') { + try { + res = JSON.parse(res) + } catch (error) { + // 和微信一致解析失败不抛出错误 + // invoke(callbackId, { + // errMsg: 'request:fail json parse error' + // }) + // return + } + } + resolve({ + data: res, + statusCode, + header: parseHeaders(xhr.getAllResponseHeaders()), + cookies: [], + }) + } + xhr.onabort = function () { + clearTimeout(timer) + reject('abort') + } + xhr.onerror = function () { + clearTimeout(timer) + reject() + } + xhr.withCredentials = withCredentials! + xhr.send(body) + return requestTask + }, + RequestProtocol, + RequestOptions +) + +function normalizeContentType(header: Record) { + const name = Object.keys(header).find( + (name) => name.toLowerCase() === 'content-type' + ) + if (!name) { + return + } + const contentType = header[name] + if (contentType.indexOf('application/json') === 0) { + return 'json' + } else if (contentType.indexOf('application/x-www-form-urlencoded') === 0) { + return 'urlencoded' + } + return 'string' +} + +/** + * 请求任务类 + */ +class RequestTask implements UniApp.RequestTask { + private _xhr?: XMLHttpRequest + constructor(xhr: XMLHttpRequest) { + this._xhr = xhr + } + abort() { + if (this._xhr) { + this._xhr.abort() + delete this._xhr + } + } + onHeadersReceived(callback: (result: any) => void): void { + throw new Error('Method not implemented.') + } + offHeadersReceived(callback: (result: any) => void): void { + throw new Error('Method not implemented.') + } +} + +/** + * 解析响应头 + * @param {string} headers + * @return {object} + */ +function parseHeaders(headers: string) { + const headersObject: Record = {} + headers.split('\n').forEach((header) => { + const find = header.match(/(\S+\s*):\s*(.*)/) + if (!find || find.length !== 3) { + return + } + headersObject[find[1]] = find[2] + }) + return headersObject +} diff --git a/packages/uni-mp-alipay/dist/uni.api.esm.js b/packages/uni-mp-alipay/dist/uni.api.esm.js index 00f37339f..22ee4c3b4 100644 --- a/packages/uni-mp-alipay/dist/uni.api.esm.js +++ b/packages/uni-mp-alipay/dist/uni.api.esm.js @@ -300,8 +300,20 @@ function wrapperOffApi(name, fn) { } }; } +function invokeSuccess(id, name, res) { + return invokeCallback(id, extend(res || {}, { errMsg: name + ':ok' })); +} +function invokeFail(id, name, err) { + return invokeCallback(id, { errMsg: name + ':fail' + (err ? ' ' + err : '') }); +} function wrapperTaskApi(name, fn, options) { - return (args) => fn.apply(null, [args, createAsyncApiCallback(name, args, options)]); + return (args) => { + const id = createAsyncApiCallback(name, args, options); + return fn(args, { + resolve: (res) => invokeSuccess(id, name, res), + reject: (err) => invokeFail(id, name, err), + }); + }; } function wrapperSyncApi(fn) { return (...args) => fn.apply(null, args); @@ -310,12 +322,8 @@ function wrapperAsyncApi(name, fn, options) { return (args) => { const id = createAsyncApiCallback(name, args, options); fn(args) - .then((res) => { - invokeCallback(id, extend(res || {}, { errMsg: name + ':ok' })); - }) - .catch((err) => { - invokeCallback(id, { errMsg: name + ':fail' + (err ? ' ' + err : '') }); - }); + .then((res) => invokeSuccess(id, name, res)) + .catch((err) => invokeFail(id, name, err)); }; } function wrapperApi(fn, name, protocol, options) { diff --git a/packages/uni-mp-baidu/dist/uni.api.esm.js b/packages/uni-mp-baidu/dist/uni.api.esm.js index 7c602bd90..2e427c83b 100644 --- a/packages/uni-mp-baidu/dist/uni.api.esm.js +++ b/packages/uni-mp-baidu/dist/uni.api.esm.js @@ -300,8 +300,20 @@ function wrapperOffApi(name, fn) { } }; } +function invokeSuccess(id, name, res) { + return invokeCallback(id, extend(res || {}, { errMsg: name + ':ok' })); +} +function invokeFail(id, name, err) { + return invokeCallback(id, { errMsg: name + ':fail' + (err ? ' ' + err : '') }); +} function wrapperTaskApi(name, fn, options) { - return (args) => fn.apply(null, [args, createAsyncApiCallback(name, args, options)]); + return (args) => { + const id = createAsyncApiCallback(name, args, options); + return fn(args, { + resolve: (res) => invokeSuccess(id, name, res), + reject: (err) => invokeFail(id, name, err), + }); + }; } function wrapperSyncApi(fn) { return (...args) => fn.apply(null, args); @@ -310,12 +322,8 @@ function wrapperAsyncApi(name, fn, options) { return (args) => { const id = createAsyncApiCallback(name, args, options); fn(args) - .then((res) => { - invokeCallback(id, extend(res || {}, { errMsg: name + ':ok' })); - }) - .catch((err) => { - invokeCallback(id, { errMsg: name + ':fail' + (err ? ' ' + err : '') }); - }); + .then((res) => invokeSuccess(id, name, res)) + .catch((err) => invokeFail(id, name, err)); }; } function wrapperApi(fn, name, protocol, options) { diff --git a/packages/uni-mp-qq/dist/uni.api.esm.js b/packages/uni-mp-qq/dist/uni.api.esm.js index f9c1d3028..b3b466c60 100644 --- a/packages/uni-mp-qq/dist/uni.api.esm.js +++ b/packages/uni-mp-qq/dist/uni.api.esm.js @@ -300,8 +300,20 @@ function wrapperOffApi(name, fn) { } }; } +function invokeSuccess(id, name, res) { + return invokeCallback(id, extend(res || {}, { errMsg: name + ':ok' })); +} +function invokeFail(id, name, err) { + return invokeCallback(id, { errMsg: name + ':fail' + (err ? ' ' + err : '') }); +} function wrapperTaskApi(name, fn, options) { - return (args) => fn.apply(null, [args, createAsyncApiCallback(name, args, options)]); + return (args) => { + const id = createAsyncApiCallback(name, args, options); + return fn(args, { + resolve: (res) => invokeSuccess(id, name, res), + reject: (err) => invokeFail(id, name, err), + }); + }; } function wrapperSyncApi(fn) { return (...args) => fn.apply(null, args); @@ -310,12 +322,8 @@ function wrapperAsyncApi(name, fn, options) { return (args) => { const id = createAsyncApiCallback(name, args, options); fn(args) - .then((res) => { - invokeCallback(id, extend(res || {}, { errMsg: name + ':ok' })); - }) - .catch((err) => { - invokeCallback(id, { errMsg: name + ':fail' + (err ? ' ' + err : '') }); - }); + .then((res) => invokeSuccess(id, name, res)) + .catch((err) => invokeFail(id, name, err)); }; } function wrapperApi(fn, name, protocol, options) { diff --git a/packages/uni-mp-toutiao/dist/uni.api.esm.js b/packages/uni-mp-toutiao/dist/uni.api.esm.js index e7d4a1e55..69c791ffe 100644 --- a/packages/uni-mp-toutiao/dist/uni.api.esm.js +++ b/packages/uni-mp-toutiao/dist/uni.api.esm.js @@ -300,8 +300,20 @@ function wrapperOffApi(name, fn) { } }; } +function invokeSuccess(id, name, res) { + return invokeCallback(id, extend(res || {}, { errMsg: name + ':ok' })); +} +function invokeFail(id, name, err) { + return invokeCallback(id, { errMsg: name + ':fail' + (err ? ' ' + err : '') }); +} function wrapperTaskApi(name, fn, options) { - return (args) => fn.apply(null, [args, createAsyncApiCallback(name, args, options)]); + return (args) => { + const id = createAsyncApiCallback(name, args, options); + return fn(args, { + resolve: (res) => invokeSuccess(id, name, res), + reject: (err) => invokeFail(id, name, err), + }); + }; } function wrapperSyncApi(fn) { return (...args) => fn.apply(null, args); @@ -310,12 +322,8 @@ function wrapperAsyncApi(name, fn, options) { return (args) => { const id = createAsyncApiCallback(name, args, options); fn(args) - .then((res) => { - invokeCallback(id, extend(res || {}, { errMsg: name + ':ok' })); - }) - .catch((err) => { - invokeCallback(id, { errMsg: name + ':fail' + (err ? ' ' + err : '') }); - }); + .then((res) => invokeSuccess(id, name, res)) + .catch((err) => invokeFail(id, name, err)); }; } function wrapperApi(fn, name, protocol, options) { diff --git a/packages/uni-mp-weixin/dist/uni.api.esm.js b/packages/uni-mp-weixin/dist/uni.api.esm.js index 712f9d66b..3955b9b0d 100644 --- a/packages/uni-mp-weixin/dist/uni.api.esm.js +++ b/packages/uni-mp-weixin/dist/uni.api.esm.js @@ -300,8 +300,20 @@ function wrapperOffApi(name, fn) { } }; } +function invokeSuccess(id, name, res) { + return invokeCallback(id, extend(res || {}, { errMsg: name + ':ok' })); +} +function invokeFail(id, name, err) { + return invokeCallback(id, { errMsg: name + ':fail' + (err ? ' ' + err : '') }); +} function wrapperTaskApi(name, fn, options) { - return (args) => fn.apply(null, [args, createAsyncApiCallback(name, args, options)]); + return (args) => { + const id = createAsyncApiCallback(name, args, options); + return fn(args, { + resolve: (res) => invokeSuccess(id, name, res), + reject: (err) => invokeFail(id, name, err), + }); + }; } function wrapperSyncApi(fn) { return (...args) => fn.apply(null, args); @@ -310,12 +322,8 @@ function wrapperAsyncApi(name, fn, options) { return (args) => { const id = createAsyncApiCallback(name, args, options); fn(args) - .then((res) => { - invokeCallback(id, extend(res || {}, { errMsg: name + ':ok' })); - }) - .catch((err) => { - invokeCallback(id, { errMsg: name + ':fail' + (err ? ' ' + err : '') }); - }); + .then((res) => invokeSuccess(id, name, res)) + .catch((err) => invokeFail(id, name, err)); }; } function wrapperApi(fn, name, protocol, options) { diff --git a/packages/uni-quickapp-webview/dist/uni.api.esm.js b/packages/uni-quickapp-webview/dist/uni.api.esm.js index b85cc0f48..d2e8ef69b 100644 --- a/packages/uni-quickapp-webview/dist/uni.api.esm.js +++ b/packages/uni-quickapp-webview/dist/uni.api.esm.js @@ -300,8 +300,20 @@ function wrapperOffApi(name, fn) { } }; } +function invokeSuccess(id, name, res) { + return invokeCallback(id, extend(res || {}, { errMsg: name + ':ok' })); +} +function invokeFail(id, name, err) { + return invokeCallback(id, { errMsg: name + ':fail' + (err ? ' ' + err : '') }); +} function wrapperTaskApi(name, fn, options) { - return (args) => fn.apply(null, [args, createAsyncApiCallback(name, args, options)]); + return (args) => { + const id = createAsyncApiCallback(name, args, options); + return fn(args, { + resolve: (res) => invokeSuccess(id, name, res), + reject: (err) => invokeFail(id, name, err), + }); + }; } function wrapperSyncApi(fn) { return (...args) => fn.apply(null, args); @@ -310,12 +322,8 @@ function wrapperAsyncApi(name, fn, options) { return (args) => { const id = createAsyncApiCallback(name, args, options); fn(args) - .then((res) => { - invokeCallback(id, extend(res || {}, { errMsg: name + ':ok' })); - }) - .catch((err) => { - invokeCallback(id, { errMsg: name + ':fail' + (err ? ' ' + err : '') }); - }); + .then((res) => invokeSuccess(id, name, res)) + .catch((err) => invokeFail(id, name, err)); }; } function wrapperApi(fn, name, protocol, options) { -- GitLab