Check out a free preview of the full Making TypeScript Stick course:
The "Penpal Types Exercise" Lesson is part of the full, Making TypeScript Stick course featured in this preview video. Here's what you'd learn in this lesson:

Students are instructed to create a utility type WrapForPenpal<T> that takes an object T with methods and emits a type with similar methods. Any non-promise return types become ”Promise-ified.”

Get Unlimited Access Now

Transcript from the "Penpal Types Exercise" Lesson

>> The last thing we're gonna do in this class is a final challenge. And this is actually, it's a typing problem that I had to solve in my journey to mastering TypeScript. So a couple years ago, I was involved in a lot of work around web workers and if you've never used these before, you can think of it as kind of a fully independent program that runs and you can talk to it through the post message API.

[00:00:28] Similar to how you can have something I framed in a larger application and use post message to go back and forth between these things. So, the post message API is excellent it's one manifestation of the actor concurrency model, which is a effective way to make sure that things are never deadlocked, where A is waiting on B and B is waiting on A.

[00:00:51] Effectively like these two programs, they each have mailboxes, they can send messages to each other, but they have no opinion on when those messages are read, can think of it that way. So it's a very good concurrency model. It's been around for a very long time. However, the way asynchronous stuff works in JavaScript now it's it's more common to use promises right?

[00:01:15] Same reason like no Jas they used to have all this everything written in callback API's and now everything's promise based right? Like reading a file you get a promise back with the contents of the file. So I found this great library called PenPal, which lets you define methods in your worker and what you end up with is a very similar method on an object in your main application that has a similar type except the return value is kind of wrapped in a promise.

[00:01:51] So you can see here this is what might be in the worker and I have this object with methods on it or functions on it. And add returns a number and then if I wanted my web worker to do this thing, I would create a connection with the web worker and get this child thing back.

[00:02:10] And I could simply invoke add on this thing and I'd get back a promise that resolves to a number. So I can kinda of have a higher level way to talk with this thing that effectively is running in a separate thread very convenient. So let me show you the read me for this library and we're gonna implement a simplified version of this, but I just want you to kind of get the idea of how this is meant to be used.

[00:02:40] So this is code that might exist in the parent window. And you can see here we've got this connect to child function. And here are your methods. Then we've got add, right? Just sums up numbers 1 and 2, and then it connects to this child and then child has a multiply and a divide.

[00:03:05] Here they are, multiply and divide to their synchronous here, up here it's kind of feels like you're just calling this thing directly. The promise Is where the data is sent over there, it's run through the function, the results collected comes back through the posts channel, and then you get it back in your parent frame.

[00:03:25] So we're not gonna worry about any of this connection aspect of things. I just want us to focus on how might we solve the interesting type information problem here. How do you take an object with some functions on it and create the type for a new object where anything that was not a promise is effectively wrapped in a promise.

[00:03:48] That's what you're setting out to do here. So that's your task you're gonna create a type called rap for PenPal you're gonna pass in type of methods which will give you the interface like corresponding to this object. And we should see that asyncmethods.add should return a promise that resolves to a number, etc, etc.

[00:04:18] Hopefully this makes sense. In this case, do not worry about making sure the code runs. We just want the type information to be what you're dealing with. So in the workshop project. You can see this async communicator folder. It's the final the three folders in this challenges area and there's a source folder in there and an index.ts.

[00:04:46] So, let me get rid of this other stuff here. In this case, there is no test suite to run. Your tests are just for type information and there at the bottom here. So here you can see that like we've got a sink methods.add. And, right now this ends up being an any.

[00:05:06] That's why we see two places where we see red squiggles. So by the time you're done, these things should go away. These are things that should error, and currently they are not erroring. That's why type scripts objecting here. So these are negative test cases. So your job is, make this work, right replace this anything with something that gives us equivalent function types with equivalent argument lists.

[00:05:36] And then anything that's not a promise should be kind of wrapped in a promise. And, I believe as an extra challenge consider that you might already have something here that's asynchronous. So if you want to a stretch goal here, don't wrap promises in double promises, right? So if we have like.

[00:06:05] Do a sync thing that returned a promise that resolves to a number something like this right? There's no need to make this a promise that resolves to a promise that resolves to a number you can just make it like leave it alone effectively. A stretch goal here because this will require a little bit extra work to handle this case you'll have to detect whether you're in that situation and either do something or not do something.

[00:06:35] So you should end up really working on line four up here only do this if you can make the rest of it work and you want to get fancy and then wrap up the course