繁体   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