簡體   English   中英

類鏈式方法中的鏈式等待

[英]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) 
})  

  • S3級
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);
});

您的實際問題是abc都引用同一個對象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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM