Issue with setting correct typings in Typescript's tapAsync function

I am interested in connecting multiple ES6 async functions using TypeScript and the tap function. The tap function should return the argument if no value is returned from the tapped function, but return the tapped value if there is a return value.

typescript playground example

I have managed to make it work without specifying types, but I am struggling with setting the correct types. Please refer to the snippet below for a working example of the code in JavaScript.

The tapped function is called simply with the x value x => fn(x), then chained to either return the value y or the tapped value x

x => fn(x).then(y => y || x)

This initial version using the any type works, but when I try to specify types in the tapped functions, I encounter errors.

const tapAsync = (fn: (x: any) => Promise<any>) => (
  x: any
): Promise<any> => fn(x).then((y: any) => y || x)

To be more specific, I am using two generics, X for the initial argument, and Y for the returned value of the tapped function.

const tapAsync = (fn: <X>(x: X) => Promise<X>) => (
  x: X
): Promise<Y|X> => fn(x).then(<Y>(y: Y) => y || x)

When calling the functions using tapAsync, I receive the following error.

src/index.ts:45:18 - error TS2345: Argument of type '({ foo }: { foo: any; }) => Promise<void>' is not assignable to parameter of type '<X>(x: X) => Promise<X>'.
  Types of parameters '__0' and 'x' are incompatible.
    Type 'X' is not assignable to type '{ foo: any; }'.

45   .then(tapAsync(one))
                    ~~~
src/index.ts:46:18 - error TS2345: Argument of type '({ foo }: { foo: any; }) => Promise<{ foo: any; bar: string; }>' is not assignable to parameter of type '<X>(x: X) => Promise<X>'.
  Types of parameters '__0' and 'x' are incompatible.
    Type 'X' is not assignable to type '{ foo: any; }'.

46   .then(tapAsync(two))
                    ~~~
src/index.ts:47:18 - error TS2345: Argument of type '({ foo, bar }: { foo: any; bar: any; }) => Promise<void>' is not assignable to parameter of type '<X>(x: X) => Promise<X>'.
  Types of parameters '__0' and 'x' are incompatible.
    Type 'X' is not assignable to type '{ foo: any; bar: any; }'.

47   .then(tapAsync(three))

I haven't specified any types in TypeScript on the tapped functions, but I have attempted to use generic types in the second function two without success

async function two<X>({ foo }): Promise<X> {
  console.log('two', foo)
  return {
    foo,
    bar: 'bar'
  }
}

async function one({ foo }) {
  console.log('one', foo)
}

async function two({ foo }) {
  console.log('two', foo)
  return {
    foo,
    bar: 'bar'
  }
}

async function three({ foo, bar }) {
  console.log('three', foo, bar)
}

const tapAsync = fn => x => fn(x).then(y => y || x)

Promise.resolve({ foo: 'foo' })
  .then(tapAsync(one))
  .then(tapAsync(two))
  .then(tapAsync(three))

Any assistance would be greatly appreciated!

============== edit 2020-09-01 ====================

I have been experimenting with the code and have added more detail to the types, but now I encounter an error with the two function when it returns a new object of the same shape.

new typescript playground example

const tapAsync = <X, Y>(fn: (x: X) => Promise<Y|void>) => 
  (x: X): Promise<X|Y> => 
    fn(x).then((y: Y|void) => y || x)

Answer №1

It seems like utilizing overloads resolves the issue in this case. Type inferencing also appears to be accurate. Essentially, by employing overloads, the TypeScript compiler can deduce that there will never be a

(x:X)=>Promise<void | Y>
returned. This situation arose because following the initial call to a non-returning async function :Promise<void>, Y was inferred to be of that type, causing the subsequent Promise.then(... to try passing X as a void | Args in the next call, resulting in a (x: void | Args)=>.... which doesn't align with the expected (x: Args)=>....

To further explore and understand this concept, check out this playground showcasing your example with overloads

function tapAsync <X,Y>(fn: (x:X)=>Promise<void>): (x:X)=>Promise<X>;
function tapAsync <X,Y>(fn: (x:X)=>Promise<Y>): (x:X)=>Promise<Y>;
function tapAsync <X, Y>(fn: (x: X) => Promise<Y|void>) {
  return (x: X) => fn(x).then(y => y||x )
}

Update: Upon reviewing my response, I omitted mentioning that the issue lies with the (y: void| Y )=> Y||X unable to determine that it cannot possibly return a void if the original X was not a void.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

The Node.js code is failing to run as anticipated

I have been working on the following code snippet. My goal is to store the numbers after each iteration of the outermost while loop in an array named sn. However, after each iteration, sn only contains the numbers from the last iteration. I suspect I might ...

The scenario involving TypeScript, MySQL, and Socket.IO where re-emitting occurs after a failed login attempt

I'm encountering an issue where, after failing to sign in and then successfully signing into my game, it creates a game instance for every failed login attempt. This same problem occurs with failed sign-up attempts as well. I would like to provide mo ...

Using jQuery to animate the display of InfoBox content on Google Maps

I'm working on a project where I have multiple markers on a map and I want an image to fade in whenever you hover over each marker. Currently, the InfoBox itself has no background image to remain invisible. The jQuery effect is supposed to be applied ...

What is the best way to generate a live map with constantly updating markers?

Is it possible for me to learn how to develop a live map similar to the one on this site: www.lightningmaps.org? It's fascinating to watch new markers pop up every few seconds. I'm interested in building a real-time map that can track IP locatio ...

Checking for the existence of a row in Node.js using Sqlite3

Wondering if it's possible to verify the existence of a row using node.js and the sqlite module. I currently have this function in place, but it always returns false due to the asynchronous nature of the module. function checkIfRowExists(username, pa ...

Stopping or pausing an HTML5 audio element in Angular with component.ts

Is there a way to create a custom function in my component.ts file to pause an HTML audio element? I can't seem to find any built-in methods for pausing audio when using document.getElement. <audio controls id="audio-file" > <source src="sam ...

I am experiencing an issue with FreeTextBox where a Javascript error is causing problems with the Post

I encountered an issue on my webpage where I have three FreeTextBox controls. Everything was working fine until I added a DropDownList control that needed to PostBack to the server. Surprisingly, the OnSelectedIndexChanged event did not trigger when using ...

What is the best method for ending a function in jQuery?

Hey everyone, I have been facing an issue with my jQuery code. I have written some code that triggers an ajax request when a button is clicked. However, the problem arises when clicking the yes or no confirmation buttons within the same function. The issue ...

Is there a way to modify HTML content in a UIWebView using JavaScript and then take a screenshot of the updated WebView? Perhaps with the help of CGD?

I'm currently using a UIWebView to display a webpage and I'm trying to hide certain elements on the page, then take a screenshot of it. Once I have the image, I will unhide the elements to return the webView back to normal. Here's my progre ...

Efficiently converting HTML object data into plain text using RegExr in ReactJS and JavaScript

Is there a way to convert an object in JavaScript/ReactJS into a string? For instance, consider the following object: { article: '<p class="md-block-unstyled">First text...</p><p>Second text></p>' } I am looking to ...

Utilizing RxJS with angular.io results in an Observable<Array<T>> structure in which T comprises an array of observables

Undoubtedly, I made an effort to search for a similar question like this one since it's hard to believe that it hasn't been asked before (even though there may be some anti-pattern involved). Let's dive into this component: @Component({...} ...

Unable to retrieve any data from BehaviorSubject within the observable

Trying to pass data inside an observable function from multiple services to an unrelated component without using service calls by utilizing BehaviorSubject has yielded mixed results. service.ts export class Data{ name:string; age:number; class:string; ...

The manifest JSON file is causing a double trouble as it returns a 404 not found error, coupled with

After carefully reviewing the suggestions in previous posts regarding my issue, I attempted to implement the solutions provided without success. I am in need of assistance to resolve this problem. My current project involves the use of React and AWS Lambda ...

How can we integrate fixed-data-table-2 sorting with an existing redux store?

Any help or advice you can offer would be greatly appreciated. I am still fairly new to using react. I recently took on a project for a client that was already in progress, and everything has been going smoothly up until now. I've come across a stumb ...

`The form input status color remains static and does not update`

I encountered a situation in one of my projects where I need to visually indicate if a field is correct or incorrect based on the value of another field. To better illustrate this issue, I have created an example here. The main challenge: I am struggling ...

Leveraging body-parser alongside formidable

I am currently in the process of integrating formidable to handle forms that involve file uploads (specifically images). Despite comments suggesting otherwise, I came across a comment demonstrating successful integration of both modules. This snippet show ...

Is it possible to reposition the vertical scrollbar to a location that is not on the left or right side?

Whenever I resize my modal on small screens, a horizontal scrollbar appears, causing the vertical scrollbar to disappear as it gets stuck on the right side. I am looking for a solution to keep the vertical scrollbar on the right side of the modal while scr ...

The desired jQuery datatable theme did not take effect on the JSP page

Experimenting with various JQuery themes and controls. Attempting to use a datatable example and apply the default theme provided here. However, I have been encountering difficulties. Seeking assistance to understand the root cause of this issue. Also, in ...

How can I retrieve documents with a specific count using MongoDB aggregation?

I've been working on this code for the past 24 hours trying to fetch all records from the last 24 hours along with their total count. db.getCollection("COLLECTION_NAME").find({"createdAt":{$gt:new Date(Date.now() - 24*60*60 * 1000)}}) ...

Guide to incorporating three-js-csg into Angular 6

Initially, the following steps are taken: npm install three --save npm install @types/three npm install three-js-csg --save Following that: import * as THREE from 'three'; import * as csg from 'three-js-csg'; let sphere1BSP = new ...