小程序没有提供 FormData 等,uploadFile 一次只能上传一个文件,只能手工撸一个了。先贴代码:
const regeneratorRuntime = require('regenerator-runtime'), _ = require('lodash.min'), {TextEncoder} = require('fast-text-encoding'), Mime = require('mime-lite');require('wxPromise');class Request { _filePromises = []; _methods = 'OPTIONS HEAD CONNECT TRACE GET POST PUT DELETE'.split(/\s+/); constructor({url, header = {}, method = 'GET', dataType = 'json', responseType = 'text', data = {}, files}) { Object.assign(this, {url, header, method, dataType, responseType, data, files}); _.each(this._methods, method => { const exec = (...args) => { this.method = method; return this.exec.apply(this, args); }; this[method] = exec.bind(this); this[method.toLowerCase()] = exec.bind(this); }); let privateProperties = _.filter(Object.keys(this), key => key[0] === '_'); _.each(privateProperties, key => Object.defineProperty(this, key, {configurable : false, enumerable : false, writable : true})); } _data = {}; get data() { return this._data; } set data(data) { if (_.isObjectLike(this._data)) Object.assign(this._data, data); else if (_.isString(this._data)) this._data += data; else if (_.isArray(this._data)) this._data = this._data.concat(data); else this._data = data; } get files() { return Promise.all(this._filePromises); } set files(files) { const addFile = file => new Promise((resolve, reject) => { file = _.isString(file) ? {name : 'files', path : file} : file; let {name = 'files', path} = file, type = 'file'; wx.Promise.FileSystemManager.readFile({filePath : path}).then(fileData => resolve({name, type, path, value : fileData.data}), reject); }); _.each(files, file => this._filePromises.push(addFile(file))); } async exec() { let countCRLF = 0; const CRLF = () => ++countCRLF && '\r\n'; // fixme: not correct, many different TypedArray const toTypedArray = buf => _.isString(buf) ? new TextEncoder().encode(buf) : _.isTypedArray(buf) ? buf : _.isArrayBuffer(buf) ? new Uint8Array(buf) : undefined; const joinBuffers = (buffers) => { if (_.every(buffers, _.isString)) { return buffers.join(''); } buffers = _.map(buffers, toTypedArray); let len = _.sumBy(buffers, buf => buf.length || buf.byteLength), result = new Uint8Array(len), start = 0; _.each(buffers, buf => { result.set(buf, start); start += buf.length; }); return result; }; const toDataBuf = (data, boundary) => { const {getType} = require('mime-lite'); let {name, type, value, path} = data; let head = ['Content-Disposition: form-data']; name && head.push(`name="${name}"`); if (type === 'file') { let contentType = Mime.getType(path) || 'application/octet-stream'; head.push(`filename="${path}"`); head = head.join('; ') + CRLF() + 'Content-Type: ' + contentType + CRLF() + CRLF(); } else { head = head.join('; ') + CRLF() + CRLF(); value = JSON.stringify(value); } return ['--' + boundary + CRLF(), head, value, CRLF()]; }; let {url, header, method = 'GET', dataType = 'json', responseType = 'text', data} = this, files = await this.files; if (_.isEmpty(files)) { return wx.Promise.request({url, data, header, method, dataType, responseType}); } let boundary = 'WeApp-----------------' + Date.now(); header['Content-Type'] = 'multipart/form-data; boundary=' + boundary; let dataPart = _.flatMap(data, (val, key) => toDataBuf({name : key, value : val}, boundary)); let filePart = _.flatMap(files, data => toDataBuf(data, boundary)); let length = _.sumBy(dataPart, "length") + _.sumBy(filePart, "length") + boundary.length - countCRLF; let payload = joinBuffers([ 'Content-Type: multipart/form-data; boundary=' + boundary + CRLF(), 'Content-Length: ' + length + CRLF() + CRLF(), ...dataPart, ...filePart, '--' + boundary + '--' ]); data = payload.buffer || payload; return wx.Promise.request({url, data, header, method, dataType, responseType}); } append(name, value) { this._data = _.isObjectLike(this._data) ? this._data : {}; Object.assign(this._data, {[name] : value}); } async appendFile(name, path) { this.files = [{name, path}]; }}module.exports = Request; |

最后 wxPromise.js
const _ = require('lodash.min');const wxMethods ="drawCanvas,createContext,createCanvasContext,canvasToTempFilePath,canvasGetImageData,canvasPutImageData,createOffscreenCanvas,getAccountInfoSync,getShareInfo,pageScrollTo,chooseInvoiceTitle,chooseInvoice,arrayBufferToBase64,base64ToArrayBuffer,openSetting,getExtConfig,chooseMedia,chooseMultiMedia,chooseMessageFile,chooseWeChatContact,uploadEncryptedFileToCDN,onUploadEncryptedFileToCDNProgress,getExtConfigSync,showShareMenu,hideShareMenu,updateShareMenu,shareAppMessageForFakeNative,openUrl,setNavigationBarColor,setNavigationBarAlpha,vibrateShort,vibrateLong,getSetting,checkIsSupportFacialRecognition,startFacialRecognitionVerify,startFacialRecognitionVerifyAndUploadVideo,startCustomFacialRecognitionVerify,startCustomFacialRecognitionVerifyAndUploadVideo,sendBizRedPacket,sendGoldenRedPacket,openGoldenRedPacketDetail,addPhoneContact,setScreenBrightness,getScreenBrightness,getWeRunData,uploadWeRunData,addWeRunData,canIUse,setPageStyle,triggerGettingWidgetData,navigateToMiniProgram,navigateToMiniProgramDirectly,navigateToDevMiniProgram,navigateBackMiniProgram,launchMiniProgram,launchApplicationDirectly,launchApplicationForNative,setNavigationBarRightButton,onTapNavigationBarRightButton,setTopBarText,setTabBarBadge,removeTabBarBadge,showTabBarRedDot,hideTabBarRedDot,showTabBar,hideTabBar,setTabBarStyle,setTabBarItem,setBackgroundColor,setBackgroundTextStyle,setEnableDebug,captureScreen,onUserCaptureScreen,setKeepScreenOn,checkIsSupportSoterAuthentication,startSoterAuthentication,checkIsSoterEnrolledInDevice,openDeliveryList,navigateBackH5,openBusinessView,navigateBackApplication,navigateBackNative,reportIDKey,reportKeyValue,setNavigationBarTitle,showNavigationBarLoading,hideNavigationBarLoading,startPullDownRefresh,stopPullDownRefresh,operateWXData,getOpenDeviceId,getMenuButtonBoundingClientRect,getSelectedTextRange,openBluetoothAdapter,closeBluetoothAdapter,getBluetoothAdapterState,onBluetoothAdapterStateChange,startBluetoothDevicesDiscovery,stopBluetoothDevicesDiscovery,getBluetoothDevices,getConnectedBluetoothDevices,createBLEConnection,closeBLEConnection,getBLEDeviceServices,getBLEDeviceCharacteristics,notifyBLECharacteristicValueChanged,notifyBLECharacteristicValueChange,readBLECharacteristicValue,writeBLECharacteristicValue,onBluetoothDeviceFound,onBLEConnectionStateChanged,onBLEConnectionStateChange,onBLECharacteristicValueChange,startBeaconDiscovery,stopBeaconDiscovery,getBeacons,onBeaconUpdate,onBeaconServiceChange,startWifi,stopWifi,getWifiList,getConnectedWifi,connectWifi,presetWifiList,setWifiList,onGetWifiList,onWifiConnected,onEvaluateWifi,getHCEState,startHCE,stopHCE,sendHCEMessage,onHCEMessage,startLocalServiceDiscovery,stopLocalServiceDiscovery,onLocalServiceFound,offLocalServiceFound,onLocalServiceLost,offLocalServiceLost,onLocalServiceDiscoveryStop,offLocalServiceDiscoveryStop,onLocalServiceResolveFail,offLocalServiceResolveFail,redirectTo,reLaunch,navigateTo,switchTab,navigateBack,onAppShow,offAppShow,onAppHide,offAppHide,onError,offError,getLaunchOptionsSync,onWindowResize,offWindowResize,getStorage,getStorageSync,setStorage,setStorageSync,removeStorage,removeStorageSync,clearStorage,clearStorageSync,getStorageInfo,getStorageInfoSync,getBackgroundFetchData,onBackgroundFetchData,setBackgroundFetchToken,getBackgroundFetchToken,request,connectSocket,closeSocket,sendSocketMessage,onSocketOpen,onSocketClose,onSocketMessage,onSocketError,uploadFile,downloadFile,addNativeDownloadTask,downloadApp,installDownloadApp,getAppInstallState,queryDownloadAppTask,cancelDownloadAppTask,resumeDownloadAppTask,pauseDownloadAppTask,onDownloadAppStateChange,downloadAppForIOS,calRqt,secureTunnel,chooseImage,previewImage,getImageInfo,saveImageToPhotosAlbum,compressImage,startRecord,stopRecord,playVoice,pauseVoice,stopVoice,onVoicePlayEnd,chooseVideo,saveVideoToPhotosAlbum,loadFontFace,getLocation,openLocation,chooseLocation,onLocationChange,startLocationUpdateBackground,startLocationUpdate,stopLocationUpdate,getNetworkType,onNetworkStatusChange,getSystemInfo,getSystemInfoSync,getBatteryInfo,getBatteryInfoSync,startAccelerometer,stopAccelerometer,onAccelerometerChange,startCompass,stopCompass,onCompassChange,startDeviceMotionListening,stopDeviceMotionListening,onDeviceMotionChange,startGyroscope,stopGyroscope,onGyroscopeChange,reportAction,getBackgroundAudioManager,getRecorderManager,getBackgroundAudioPlayerState,playBackgroundAudio,pauseBackgroundAudio,seekBackgroundAudio,stopBackgroundAudio,onBackgroundAudioPlay,onBackgroundAudioPause,onBackgroundAudioStop,joinVoIPChat,exitVoIPChat,updateVoIPChatMuteConfig,onVoIPChatMembersChanged,onVoIPChatSpeakersChanged,onVoIPChatInterrupted,login,checkSession,authorize,getUserInfo,requestPayment,verifyPaymentPassword,bindPaymentCard,requestPaymentToBank,requestVirtualPayment,openOfflinePayView,openWCPayCardList,requestMallPayment,setCurrentPaySpeech,loadPaySpeechDialectConfig,faceVerifyForPay,openOfficialAccountProfile,openUserProfile,openMiniProgramProfile,openMiniProgramSearch,openMiniProgramHistoryList,openMiniProgramStarList,batchGetContactDirectly,preventApplePayUI,getWxSecData,addCard,openCard,scanCode,openQRCode,chooseAddress,saveFile,openDocument,getSavedFileList,getSavedFileInfo,getFileInfo,removeSavedFile,getFileSystemManager,getABTestConfig,chooseContact,removeUserCloudStorage,setUserCloudStorage,makePhoneCall,makeVoIPCall,onAppRoute,onAppRouteDone,onAppEnterBackground,onAppEnterForeground,onAppUnhang,onPageReload,onPageNotFound,offPageNotFound,createAnimation,createInnerAudioContext,getAvailableAudioSources,onAudioInterruptionBegin,offAudioInterruptionBegin,onAudioInterruptionEnd,offAudioInterruptionEnd,setInnerAudioOption,createAudioContext,createVideoContext,createMapContext,createCameraContext,createLivePlayerContext,createLivePusherContext,onWebviewEvent,onNativeEvent,hideKeyboard,onKeyboardHeightChange,getPublicLibVersion,showModal,showToast,hideToast,showLoading,hideLoading,showActionSheet,showShareActionSheet,reportAnalytics,reportMonitor,getClipboardData,setClipboardData,createSelectorQuery,createIntersectionObserver,nextTick,updatePerfData,traceEvent,onMemoryWarning,getUpdateManager,createWorker,voiceSplitJoint,uploadSilkVoice,downloadSilkVoice,getResPath,setResPath,setCookies,getCookies,getLabInfo,setLabInfo,createUDPSocket,isSystemError,isSDKError,isThirdError,createRewardedVideoAd,createInterstitialAd,getLogManager,getRealtimeLogManager,chooseShareGroup,enterContact".split(',');const wxPromiseTimeout = 2000, notWatching = ['chooseImage'];wx.Promise = {FileSystemManager : {}};wxMethods.forEach(name => {wx.Promise[name] = (...args) =>newPromise((resolve, reject) => {let object = args[0], watching = notWatching.indexOf(name) === -1;const __resolve = watching ? (...args) => {watching =false;resolve.apply(this, args);} : resolve;const __reject = watching ? (...args) => {watching =false;reject.apply(this, args);} : resolve;if(typeofobject ==='object') {let {success, fail} = object;object.success = res => {if(typeofsuccess ==='function')res = success(res);__resolve(res);};object.fail = err => {if(typeoffail ==='function')err = fail(err);__reject(err);};}else{args = [{success : __resolve, fail : __reject}];}wx[name].apply(wx, args);watching && setTimeout(() => {if(watching)console.log(`%c wx.${name} did not complete after ${wxPromiseTimeout}ms`,'color:red;font-size:16px;');}, wxPromiseTimeout);});});const fsMethods ="access,appendFile,copyFile,getFileInfo,getSavedFileInfo,getSavedFileList,mkdir,readFile,readdir,removeSavedFile,rename,rmdir,saveFile,stat,unlink,unzip,writeFile".split(',');let FileSystemManager = wx.getFileSystemManager();fsMethods.forEach(name => {wx.Promise.FileSystemManager[name] = (...args) =>newPromise((resolve, reject) => {let object = args[0];if(typeofobject ==='object') {object.success = resolve;object.fail = reject;}FileSystemManager[name].apply(FileSystemManager, args);});});可以看一下这个例子:https://juejin.im/post/5e561e13f265da5754779316
实现中使用了Uint8Array,在基础库2.9.2之前不支持,会报错!
$ npm i -s mime
$ browserify -s Mime -r mime/lite -o miniprogram_npm/mime-lite.js
$npm i -s fast-text-encoder
$ cp node_modules/fast-text-encoder/text.min.js miniprogram_npm/fast-text-encoder.js
把尾部这部分
("undefined"!==typeof window?window:"undefined"!==typeof global?global:this)
替换成
$ npm i -s lodash
$ cp node_modules/lodash/lodash.min.js miniprogram_npm
在文件首部增加一行:
Object.assign(global, {Array, Date, Error, Function, Math, Object, RegExp, String, TypeError, setTimeout, clearTimeout, setInterval, clearInterval});用到几个第三方库,分别说明一下。
$ npm i -s regenerator-runtime
$ cp node_modules/regenerator-runtime/runtime.js miniprogram_npm/regenerator-runtime.js
注释掉最后一段 try ... catch
使用方法,参数与 wx.request 一致,增加 files: