I'm using Axios to handle some API fetching, and I'm executing that call within a generator; async/await
is not an option for this particular project. For some reason, even though axios
is getting my types right, typescript is inferring an any
type when I use the yield
keyword.
function* foo() {
// axios.get is returning Promise<AxiosResponse<T>>
// but data is being inferred as "any"
const { data } = yield axios.get<T>(url);
// do some stuff with data
}
If I specfically type the axios response, it works fine, but I feel like I'm missing something, since TS isn't getting the type automatically
function* foo() {
const { data }: AxiosResponse<T> = yield axios.get<T>(url);
// do some stuff with data
}
Is there some other step or config I'm missing?
Here's my tsconfig
{
"compilerOptions": {
"baseUrl": "./",
"outDir": "./dist/",
"sourceMap": true,
"noImplicitAny": false,
"module": "commonjs",
"target": "es6",
"jsx": "react",
"removeComments": true,
"allowJs": true,
"skipLibCheck": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"rootDirs": ["src", "src/stories"],
"paths": {
"*": ["./src/custom-types/*"]
},
"types": ["@emotion/core", "jest"]
},
"include": ["./src/**/*"]
}
Preface: You've said you can't use async
/await
because you're using mobx-state-tree
, which says you can't use it on protected trees. But see this article on how to use async
/await
successfully with it. (Basically, instead of directly updating things in your async
function, you call an action to do it.) It's on the blog of Tyler Williams, one of the contributors to mobx-state-tree
. I found it in March 2024; no idea when it was written, it's undated ☹️ and not in archive.org. He says they're working on updating the mobx-state-tree
documentation.
Answering the generator question:
TypeScript can't infer the type of the yield
operation, because that's controlled by the calling code. In your case, it sounds like this generator is being used by code that handles the promise from axios and responds by providing the axios result in the call to g.next
. That makes sense if you're in an environment where you can't use async
/await
; generators can be used to allow async logic to flow more clearly, where the generator is driven by a helper that gets the promise from the generator, waits for it to settle, then passes the fulfillment value back to the generator via next
— yield
largely takes the role of await
when doing that. (co
is one example of a library that enables using generators this way.) So the code using the generator expects the generator to yield a Promise
and it gives back the promise's fulfillment value. In this case, that would be yielding a Promise<AxiosResponse<T>>
and getting back an AxiosResponse<T>
.
To handle that, you need to annotate the function using the Generator
type, which accepts three type parameters:
T
- the type of what the generator produces via yield
.TReturn
- the type of what the generator returns when done.TNext
- the type of what the generator consumes (receives from yield
).So applying that to your example, we'd add a generic type parameter to foo
and annotate it with Generator
:
function* foo<T>(): Generator<Promise<AxiosResponse<T>>, ReturnType, AxiosResponse<T>> {
const { data } = yield axios.get<T>(url);
// ...do some stuff with data...
return /*...something of `ReturnType` (or use `void` as the type argument above)... */;
}
Just for anyone who isn't as familiar with yield
and generators as they might like, here's an example where the generator produces strings, returns a boolean, and consumes numbers (playground link):
function* example(): Generator<string, boolean, number> {
const a = yield "a";
console.log(`Received ${a} for a`);
const b = yield "b";
console.log(`Received ${b} for b`);
const c = yield "c";
console.log(`Received ${c} for c`);
const flag = (a + b) > c;
return flag;
}
const g = example();
let result = g.next();
console.log(result.value); // "a"
result = g.next(1); // Passes 1 to the generator
// (Generator logs "Received 1 for a")
console.log(result.value); // "b"
result = g.next(2); // Passes 2 to the generator
// (Generator logs "Received 2 for b")
console.log(result.value); // "c"
result = g.next(3); // Passes 3 to the generator
// (Generator logs "Received 3 for c")
console.log(result.value); // false (1 + 2 > 3 is false)