小程序没有提供 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) =>
new
Promise((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
(
typeof
object ===
'object'
) {
let {success, fail} = object;
object.success = res => {
if
(
typeof
success ===
'function'
)
res = success(res);
__resolve(res);
};
object.fail = err => {
if
(
typeof
fail ===
'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) =>
new
Promise((resolve, reject) => {
let object = args[0];
if
(
typeof
object ===
'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
在文件首部增加一行:
用到几个第三方库,分别说明一下。
$ npm i -s regenerator-runtime
$ cp node_modules/regenerator-runtime/runtime.js miniprogram_npm/regenerator-runtime.js
注释掉最后一段 try ... catch
使用方法,参数与 wx.request 一致,增加 files: