How to properly deal with promisifyAll in typescript?
Asked Answered
G

4

14

Consider the following code:

import redis = require('redis');  //Has ambient declaration from DT
import bluebird = require('bluebird');  //Has ambient declaration from DT

bluebird.promisifyAll((<any>redis).RedisClient.prototype);
bluebird.promisifyAll((<any>redis).Multi.prototype);

const client = redis.createClient();

client.getAsync('foo').then(function(res) {
    console.log(res);
});

getAsync will error out because it's created on the fly and not defined in any .d.ts file. So what is the proper way to handle this?

Also, even though I have the .d.ts files loaded for redis, I still need to cast redis to any to be used for promisifyAll. Otherwise, it will spill out error:

Property 'RedisClient' does not exist on type 'typeof "redis"'

Is typing it to any the only easy way to go?

Gadget answered 12/4, 2016 at 3:23 Comment(2)
Have you ever found a way to resolve this? I am running into same issue...Poultice
@Poultice Yes by using declaration merging. See my answer below.Gadget
G
12

I'm solving this by declaration merging the setAsync & getAsync methods. I added the following code in my own custom .d.ts file.

declare module "redis" {

    export interface RedisClient extends NodeJS.EventEmitter {
        setAsync(key:string, value:string): Promise<void>;
        getAsync(key:string): Promise<string>;
    }

}
Gadget answered 14/4, 2016 at 20:55 Comment(4)
is this maintainable for large libraries? What if you promifisy a lib that has a lots of method - would you have to re-declare all *Async() methods? Because that would be tedious...Poultice
I think explicitly declaring them is essentially the point of using Typescript. The alternative is to use <any> which basically returns to the dynamic nature (of javascript).Gadget
One could implement some sort of offline script that would be a static counterpart of Promise.promisifyAll. It would take a reference to original typings and would produce an "appendfix" with *Async functions.Weeper
really hope typescript team can add some promisifyAll in the beautiful languageCartilaginous
A
6

Another way to do it which requires less code is to extend the Redis object like so:

import { promisify } from 'util';
import { ClientOpts, RedisClient } from 'redis';

class AsyncRedis extends RedisClient {
  public readonly getAsync = promisify(this.get).bind(this);
  public readonly setAsync = promisify(this.set).bind(this);
  public readonly quitAsync = promisify(this.quit).bind(this);
  public readonly rpushAsync: (list: string, item: string) => Promise<number> = promisify(
    this.rpush
  ).bind(this);
  public readonly blpopAsync: (
    list: string,
    timeout: number
  ) => Promise<[string, string]> = promisify(this.blpop).bind(this);
  public readonly flushdbAsync = promisify(this.flushdb).bind(this);
}

Notice that not all method signatures overwrite correctly, so you have to help typescript a little.

Now you can just use this enhanced class by creating it with your options, for example:

new AsyncRedis({
  host: process.env.REDIS_HOST || '127.0.0.1',
  password: process.env.REDIS_PASSWORD || 'whatever',
 });
Angelicaangelico answered 30/12, 2019 at 6:52 Comment(0)
A
1

Just adding to Dave's answer, in my needs, I has to add in Multi for atomic operations.

declare module 'redis' {
    export interface RedisClient extends NodeJS.EventEmitter {
        execAsync(...args: any[]): Promise<any>;
        hgetallAsync(...args: any[]): Promise<any>;
        // add other methods here
    }
    export interface Multi extends Commands<Multi> {
        execAsync(...args: any[]): Promise<any>;
        // add other methods here
    }
}

Apocarp answered 31/7, 2019 at 20:10 Comment(0)
G
0

This solution works fine for me:

import { promisifyAll } from 'bluebird'; // import here works only if @types/bluebird is installed
import redis, { RedisClient, Multi } from 'redis'; // import here works only if @types/redis is installed

// Convert Redis client API to use promises, to make it usable with async/await syntax
const MultiAsync: any = promisifyAll(Multi.prototype);
const RedisClientAsync: any = promisifyAll(RedisClient.prototype);
const redisAsync = { ...redis, Multi: MultiAsync, RedisClient: RedisClientAsync };

const client: typeof RedisClientAsync = redisAsync.createClient();
// now you can use client async methods, i.e. client.getAsync, client.hgetAsync, client.hsetAsync, client.expireAsync...
Geis answered 25/10, 2021 at 10:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.