Проблема
Что делает TypeScript?
Алгоритм TypeScript для оценки типа операнда для await
выглядит примерно так (это очень упрощенное объяснение) [ ссылка ] :
- Тип является обещанием? Если да, от go до шаг 1 с обещанным типом. Если нет от go до шаг 2 .
- Является ли тип применимым? Если нет, верните тип. Если да, выдает ошибку типа: «Тип операнда 'await' должен быть действительным обещанием или не должен содержать вызываемого члена 'then' (ts1320)».
Что TypeScript делает в вашем пример?
Теперь, зная это, мы можем увидеть, что делает TypeScript при проверке вашего кода.
Операнд для await
:
new QueryBuilder(Model.find())
.limit(5)
.skip(5)
- Вызов
skip
не возвращает обещание. Мы от go до шаг 2 (Примечание: ни вызов limit
, ни создание экземпляра QueryBuilder
). skip
возвращает экземпляр QueryBuilder
, который имеет вызываемого члена then
. Это приводит к ошибке типа: «Тип операнда 'await' должен быть действительным обещанием или не должен содержать вызываемого члена 'then' (ts1320)».
Определение вашего класса с вызываемым Элемент 'then':
class QueryBuilder {
public query: Cursor;
constructor(query: Cursor) {
this.query = query;
}
...
public then(cb: Function) {
cb(this.query.toArray());
}
}
Почему возникает ошибка TypeScript?
Теперь мы понимаем, как TypeScript выдал ошибку типа. Но почему он выдает эту ошибку? JavaScript позволяет вам await
на что угодно .
[rv] = await expression;
выражение: обещание или любое значение, которого нужно ждать. rv: возвращает выполненное значение обещания или само значение, если оно не является обещанием.
- Документация MDN для await
Почему TypeScript говорит: «Тип операнда 'await' [если это недопустимое обещание] не должен содержать вызываемого члена 'then'»? Почему он не позволяет await
на затемненном компьютере? MDN даже дает пример, в котором вы await
в таблице.
async function f2() {
const thenable = {
then: function(resolve, _reject) {
resolve('resolved!')
}
};
console.log(await thenable); // resolved!
}
f2();
- Пример MDN await
в таблице
Исходный код TypeScript хорошо прокомментирован. Он гласит:
Тип не был обещанием, поэтому его нельзя было развернуть дальше. Пока тип не имеет вызываемого свойства «then», возвращать тип безопасно; в противном случае выдается сообщение об ошибке, и мы возвращаем значение undefined.
Примером «thenable» без обещания может быть:
await { then(): void {} }
«thenable» не соответствует минимальному определению для Обещание. Когда обещание, совместимое с Promise / A + или ES6, пытается принять это значение, обещание никогда не будет выполнено. Мы рассматриваем это как ошибку, чтобы помочь выявить ранний индикатор проблемы во время выполнения. Если пользователь хочет вернуть это значение из функции asyn c, ему нужно будет обернуть его другим значением. Если они хотят, чтобы это рассматривалось как обещание, они могут привести к <any>
.
- Ссылка
Из прочтения я понял, что TypeScript не выполняет await
в таблицах, не являющихся обещаниями, потому что не может гарантировать, что реализация соответствует минимуму SPE c, как определено Promises / A + , таким образом, предполагается, что это ошибка.
Комментарии к ваше решение
В решении, которое вы пробовали и добавили в контекст вашего вопроса, вы определили класс QueryBuilder
для расширения собственного обещания, а затем вы переопределили then
член. Хотя это, похоже, дает желаемый эффект, есть некоторые проблемы:
Создание экземпляра вашего класса имеет неразумное поведение
В результате расширения класса вам необходимо вызвать его родительский конструктор, прежде чем вы сможете ссылаться контекст this
. Тип конструктора родительского класса:
(resolve: (value?: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void
И, как вы обнаружили, вам нужно передать что-то , удовлетворяющее этому контракту, только для того, чтобы заставить его работать. . Также после создания экземпляра ваш класс возвращает обещание, разрешенное с произвольным значением. Код, который не имеет значения и является необоснованным, является источником путаницы, неожиданного поведения и может привести к ошибкам.
Вы нарушили контракт типа, определенный интерфейсом обещания
Интерфейс определяет другие методы, такие как catch
. Потребитель класса может попытаться использовать метод catch
, и хотя контракт позволяет ему это сделать, поведение не такое, как ожидалось. Это ведет к следующему пункту.
Вы не используете TypeScript в своих интересах
Вы упомянули в своем вопросе:
Код в ванили JavaScript работал довольно хорошо, но когда я попытался написать его на TypeScript, я получил ошибку
Типы существуют по определенной причине, одна из которых заключается в том, что они уменьшают вероятность ошибок, обеспечивая выполнение контракта между интерфейсами. Если вы попытаетесь их обойти, это приведет к путанице, неожиданному поведению и повышенному риску ошибок. Зачем вообще использовать TypeScript?
Решение
Теперь, когда мы понимаем, в чем заключается ошибка и почему она возникает, мы можем придумать решение. Решение потребует реализации члена then
для соответствия минимальному spe c в Promises / A +, поскольку мы определили, что это является причиной ошибки. Нас интересует только spe c для интерфейса then
, а не детали его реализации:
- 2.2.1 Оба
onFulfilled
и onRejected
являются необязательные аргументы - 2.2.7
then
должны возвращать обещание
Определение TypeScript для then
также полезно для ссылки (примечание: I ' Мы внесли некоторые изменения для удобочитаемости):
/**
* Attaches callbacks for the resolution and/or rejection of the Promise.
* @param onfulfilled The callback to execute when the Promise is resolved.
* @param onrejected The callback to execute when the Promise is rejected.
* @returns A Promise for the completion of which ever callback is executed.
*/
then<
TResult1 = T,
TResult2 = never
>(
onfulfilled?:
| ((value: T) => TResult1 | PromiseLike<TResult1>)
| undefined
| null,
onrejected?:
| ((reason: any) => TResult2 | PromiseLike<TResult2>)
| undefined
| null
): Promise<TResult1 | TResult2>;
Сама реализация, скорее всего, будет следовать этому алгоритму:
- Выполнить ваш собственный журнал c
- Если шаг 1 было успешно разрешено с результатом вашего пользовательского logi c, в противном случае отклоните с ошибкой
Вот демонстрация примера реализации, которая должна помочь вам начать собственная реализация.
class CustomThenable {
async foo() {
return await 'something';
}
async then(
onFulfilled?: ((value: string) => any | PromiseLike<string>) | undefined | null,
): Promise<string | never> {
const foo = await this.foo();
if (onFulfilled) { return await onFulfilled(foo) }
return foo;
}
}
async function main() {
const foo = await new CustomThenable();
console.log(foo);
const bar = await new CustomThenable().then((arg) => console.log(arg));
console.log(bar);
}
main();