![](/img/trans.png)
[英]Testcafe expect passes if await used but not if methods chained
[英]chained await in class chained methods
上下文:兩個單獨文件中的 javascript 類,每個都集成不同的外部服務並在 express.js 路由器中調用。
請參閱下面的“有問題的代碼”:
routes.post('/aws', upload.single('file'), async (req, res) => {
const transcribeParams = JSON.parse(req.body.options)
const bucket = 'bucket-name'
const data = await ( await ( await awsTranscribe.Upload(req.file, bucket)).CreateJob(transcribeParams)).GetJob()
res.send(data)
})
class AmazonS3 {
constructor() {
this.Upload = this.Upload
}
async Upload(file, bucket) {
const uploadParams = {
Bucket: bucket,
Body: fs.createReadStream(file.path),
Key: file.filename,
}
this.data = await s3.upload(uploadParams).promise()
return this
}
}
class Transcribe extends AwsS3 {
constructor() {
super()
this.CreateJob = this.CreateJob
this.GetJob = this.GetJob
}
async CreateJob(params) {
if(this.data?.Location) {
params.Media = { ...params.Media, MediaFileUri: this.data.Location }
}
this.data = await transcribeService.startTranscriptionJob(params).promise()
return this
}
async GetJob(jobName) {
if(this.data?.TranscriptionJob?.TranscriptionJobName) {
jobName = this.data.TranscriptionJob.TranscriptionJobName
}
this.data = await transcribeService.getTranscriptionJob({TranscriptionJobName: jobName}).promise()
return this
}
}
問題:問題在於路由器文件中的鏈式等待:
await ( await ( await awsTranscribe.Upload...
是的,它確實有效,但是將來另一個人維護此代碼將是可怕的。
我該怎么做才能做到
awsTranscribe.Upload(req.file, bucket).CreateJob(transcribeParams).GetJob()
沒有 .then?
問題在於路由器文件中的鏈式等待:
await ( await ( await awsTranscribe.Upload...
不,沒關系。 特別是,將其重構為單獨的行將是微不足道的:
routes.post('/aws', upload.single('file'), async (req, res) => {
const transcribeParams = JSON.parse(req.body.options)
const bucket = 'bucket-name'
const a = await awsTranscribe.Upload(req.file, bucket);
const b = await b.CreateJob(transcribeParams);
const c = await b.GetJob();
res.send(c);
});
您的實際問題是a
、 b
和c
都引用同一個對象awsTranscribe
。 如果編寫了您的代碼也將“工作”
routes.post('/aws', upload.single('file'), async (req, res) => {
const transcribeParams = JSON.parse(req.body.options)
const bucket = 'bucket-name'
await awsTranscribe.Upload(req.file, bucket);
await awsTranscribe.CreateJob(transcribeParams);
await awsTranscribe.GetJob();
res.send(awsTranscribe);
});
可怕的是,您正在通過可變的awsTranscribe.data
屬性在這些方法之間傳遞數據——甚至在不同時間在其中存儲不同類型的數據! 可以更改方法調用的順序,它會以不明顯且難以調試的方式完全中斷。
此外,多個請求似乎共享同一個awsTranscribe
實例。 這不適用於並發請求。 從“不工作”到使用來自不同用戶(請求)的工作數據進行響應,一切皆有可能! 你絕對需要修復它,然后再看看丑陋的語法。
你真正應該做的是擺脫class
es。 這里沒有理由使用有狀態對象,這是簡單的程序代碼。 編寫簡單的函數,接受參數和返回值:
export async function uploadFile(file, bucket) {
const uploadParams = {
Bucket: bucket,
Body: fs.createReadStream(file.path),
Key: file.filename,
};
const data = s3.upload(uploadParams).promise();
return data.Location;
}
export async function createTranscriptionJob(location, params) {
params = {
...params,
Media: {
...params.Media,
MediaFileUri: location,
},
};
const data = await transcribeService.startTranscriptionJob(params).promise();
return data.TranscriptionJob;
}
async function getTranscriptionJob(job) {
const jobName = job.TranscriptionJobName;
return transcribeService.getTranscriptionJob({TranscriptionJobName: jobName}).promise();
}
然后您可以導入並將它們稱為
routes.post('/aws', upload.single('file'), async (req, res) => {
const transcribeParams = JSON.parse(req.body.options)
const bucket = 'bucket-name'
const location = await uploadFile(req.file, bucket);
const job = await createTranscriptionJob(location, transcribeParams);
const data = await getTranscriptionJob(job);
res.send(c);
});
我對是否可以使用多個async
方法獲取一個對象並以某種方式使它們自動鏈接起來很感興趣。 好吧,你可以:
function chain(obj, methodsArray) { if (!methodsArray || !methodsArray.length) { throw new Error("methodsArray argument must be array of chainable method names"); } const methods = new Set(methodsArray); let lastPromise = Promise.resolve(); const proxy = new Proxy(obj, { get(target, prop, receiver) { if (prop === "_promise") { return function() { return lastPromise; } } const val = Reflect.get(target, prop, receiver); if (typeof val !== "function" || !methods.has(prop)) { // no chaining if it's not a function // or it's not listed as a chainable method return val; } else { // return a stub function return function(...args) { // chain a function call lastPromise = lastPromise.then(() => { return val.apply(obj, args); //return Reflect.apply(val, obj, ...args); }); return proxy; } } } }); return proxy; } function delay(t) { return new Promise(resolve => { setTimeout(resolve, t); }); } function log(...args) { if (!log.start) { log.start = Date.now(); } const delta = Date.now() - log.start; const deltaPad = (delta + "").padStart(6, "0"); console.log(`${deltaPad}: `, ...args) } class Transcribe { constructor() { this.greeting = "Hello"; } async createJob(params) { log(`createJob: ${this.greeting}`); return delay(200); } async getJob(jobName) { log(`getJob: ${this.greeting}`); return delay(100); } } const t = new Transcribe(); const obj = chain(t, ["getJob", "createJob"]); log("begin"); obj.createJob().getJob()._promise().then(() => { log("end"); });
Transcribe
類有一個占位符,它有兩個返回承諾的異步方法。
然后,有一個chain()
函數返回一個對象的代理,使一組傳入的方法名稱可鏈接,這允許您執行以下操作:
const t = new Transcribe();
// make chainable proxy
const obj = chain(t, ["getJob", "createJob"]);
obj.createJob().getJob()
或者
await obj.createJob().getJob()._promise()
我不一定會說這是生產就緒代碼,但這是一個有趣的可行性演示,並且(對我而言)是一個了解更多關於 Javascript 代理對象的機會。
這是一種不同的方法,它(而不是代理對象)將方法存根添加到 promise 以使事物可鏈接:
function chain(orig, methodsArray) { let masterP = Promise.resolve(); function addMethods(dest) { for (const m of methodsArray) { dest[m] = function(...args) { // chain onto master promise to force sequencing masterP = masterP.then(result => { return orig[m].apply(orig, ...args); }); // add methods to the latest promise befor returning it addMethods(masterP); return masterP; } } } // add method to our returned promise addMethods(masterP); return masterP; } function delay(t) { return new Promise(resolve => { setTimeout(resolve, t); }); } function log(...args) { if (!log.start) { log.start = Date.now(); } const delta = Date.now() - log.start; const deltaPad = (delta + "").padStart(6, "0"); console.log(`${deltaPad}: `, ...args) } class Transcribe { constructor() { this.greeting = "Hello"; this.cntr = 0; } async createJob(params) { log(`createJob: ${this.greeting}`); ++this.cntr; return delay(200); } async getJob(jobName) { log(`getJob: ${this.greeting}`); ++this.cntr; return delay(100); } } const t = new Transcribe(); log("begin"); chain(t, ["getJob", "createJob"]).createJob().getJob().then(() => { log(`cntr = ${t.cntr}`); log("end"); });
由於這會返回一個實際的 Promise(附加了其他方法),因此您可以直接使用.then()
或await
它,而無需第一個實現所需的單獨._promise()
。
因此,您現在可以執行以下操作:
const t = new Transcribe();
chain(t, ["getJob", "createJob"]).createJob().getJob().then(() => {
log(`cntr = ${t.cntr}`);
});
或者:
const t = new Transcribe();
await chain(t, ["getJob", "createJob"]).createJob().getJob();
log(`cntr = ${t.cntr}`);
而且,這是第三個版本,它創建了一個 thenable 對象(一個偽承諾),並在其上添加了方法(如果它打擾您向現有承諾添加方法):
function chain(orig, methodsArray) { if (!methodsArray || !methodsArray.length) { throw new Error("methodsArray argument must be array of chainable method names"); } let masterP = Promise.resolve(); function makeThenable() { let obj = {}; for (const m of methodsArray) { obj[m] = function(...args) { // chain onto master promise to force sequencing masterP = masterP.then(result => { return orig[m].apply(orig, ...args); }); return makeThenable(); } } obj.then = function(onFulfill, onReject) { return masterP.then(onFulfill, onReject); } obj.catch = function(onReject) { return masterP.catch(onReject); } obj.finally = function(onFinally) { return masterP.finally(onFinally); } return obj; } return makeThenable(); } function delay(t) { return new Promise(resolve => { setTimeout(resolve, t); }); } function log(...args) { if (!log.start) { log.start = Date.now(); } const delta = Date.now() - log.start; const deltaPad = (delta + "").padStart(6, "0"); console.log(`${deltaPad}: `, ...args) } class Transcribe { constructor() { this.greeting = "Hello"; this.cntr = 0; } async createJob(params) { log(`createJob: ${this.greeting}`); ++this.cntr; return delay(200); } async getJob(jobName) { log(`getJob: ${this.greeting}`); ++this.cntr; return delay(100); } } const t = new Transcribe(); log("begin"); chain(t, ["getJob", "createJob"]).createJob().getJob().then(() => { log(`cntr = ${t.cntr}`); log("end"); });
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.