0

I'm trying to build a crypto withdraw system and need to make sure the system doesn't duplicate, etc. so I've decided to use Agenda to help me out. The problem I've come into is the fact I need to listen for the completion of the job in order to retrieve the transaction ID from the newly-sent transaction. This will only be available after the job is complete and I can't find anything about using a listener for one job.

Here is a section of my code:

let pendingUserWithdrawJob = await Job.count({ name: "withdraw_order", "data.userId": user._id, completed: { $ne: true } });
    if (pendingUserWithdrawJob > 0) return 1;

    const job = global.agenda.create('withdraw_order', {userId: user._id, recipientAddress: addr, amount, txid: null });
    job.save((err) => {
        if (err) return false;

        return msg.reply('Successfully withdrawn! TXID: ' + job.attrs.data.txid);
    });

This is before any kind of checking for completion, and the txid returns null, which is expected.

Here is my agenda define code:

agenda.define('withdraw_order', async function(job, done) {
    let result = await paymentProcessor.performWithdraw(job);
    if (result.error) return done(result.error);
    done();
});

The performWithdraw function in question is:

async performWithdraw(options) {
    try {
        await this.withdraw(options);
        return { success: true };
    } catch(e) {
        this.reportException(e);
        return { error: e };
    }
}

this.withdraw:

async withdraw(job) {
    // parameters
    const userId            = job.attrs.data.userId;
    const recipientAddress  = job.attrs.data.recipientAddress;
    const amount            = job.attrs.data.amount;

    // Validate if user is present
    let user = await models.User.findById(userId);
    if (!user) throw new Error(`User ${userId} not found`);
    await models.User.validateWithdrawAmount(user, amount);

    // Step 1: Process transaction
    let sendID;

    if (job.attrs.sendStepCompleted) {
        sendID = job.attrs.txid;
    } else {
        const sent = await this.pivxClient.send(recipientAddress, amount);
        if (sent.error) throw new Error(sent.error);
        await models.Job.findOneAndUpdate({ _id: job.attrs._id} , { "data.sendStepCompleted": true, "data.txid": sent.txid });
    }

    // Step 2: Update user balance
    if (!job.attrs.userStepCompleted) {
        await models.User.withdraw(user, amount);
        await models.Job.findByIdAndUpdate(job.attrs._id, { "data.userStepCompleted": true });
    }

    // Step 3: Record Transaction
    if (!job.attrs.transactionStepCompleted) {
        await models.Transaction.create({ userId: userId, withdraw: amount, txid: sendID });
        await models.Job.findByIdAndUpdate(job.attrs._id, { "data.transactionStepCompleted": true });
    }

    return sendID;
}

Apologies for the mess of code in advance, I just wanted to make sure I didn't leave anything out that could be important. How can I listen for the job to be done without using setInterval and checking for transactionStepCompleted?

DaJuukes
  • 49
  • 6

1 Answers1

0

Use Job Queue Events

An instance of an agenda will emit the following events:

start - called just before a job starts

start:job name - called just before the specified job starts

complete - called when a job finishes, regardless of if it succeeds or fails

complete:job name - called when a job finishes, regardless of if it succeeds or fails

https://github.com/agenda/agenda#job-queue-events


agenda.on('start', job => {
  console.log('Job %s starting', job.attrs.name);
});

agenda.on('complete', job => {
  console.log(`Job ${job.attrs.name} finished`);
});
Sam Quinn
  • 3,310
  • 1
  • 16
  • 11