في تايب سكريبت، كيف يمكن تحديد نوع الدالة غير المتزامنة


حاولت تعريف نوع من الدوال غير المتزامنة، لكن فشل التجميع، انظر أدناه:

interface SearchFn {
    async (subString: string): string;
}

class A {
    private Fn: SearchFn
    public async do():Promise<string> {
        await this.Fn("fds") // complain here: cannot invoke an expression whose type lacks a call signature
        return ''
    }
}

هل يمكن لأي شخص مساعدتي في حل هذا؟

الإجابة رقم 1

ينجح ذلك إذا قمت فقط بتحديد نوع الإرجاع للدالة ليكون Promise:

interface SearchFn {
    (subString: string): Promise<boolean>;
}

أو كتصريح نوع:

type SearchFn = (subString: string) => Promise<boolean>;

سيقوم مُحلل TS من مايكروسوفت بالتوصية بهذه الصياغة الثانية.

الإجابة رقم 2

يتم استخدام الكلمة المفتاحية async للإشارة إلى المترجم/وقت التشغيل بأن الدالة المعنية ستستخدم await داخليًا (حتى يتمكن من وضع البنية اللازمة لتمكين ذلك).

هذا يعني أن async له معنى فقط بالنسبة لـ تنفيذ الدالة، وليس الواجهة الخاصة بها. لذلك، وجود async على طريقة في واجهة ليس مفيدًا، أنت تريد أن تقول أن الدالة تعيد Promise معينة (في حالتك Promise<string>) لكنك لا تريد فرض أن يقوم منفذ الواجهة بتنفيذ هذا بطريقة معينة (باستخدام await).

كما قال آخرون قبلي:

interface SearchFn {
    (subString: string): Promise<string>;
}

ثم، من يختار تنفيذ هذه الدالة يمكنه اختيار استخدام async، أو Promise.then التقليدي، أو ربما حتى بعض المنهجيات الجديدة التي ستظهر في المستقبل.

الإجابة رقم 3

مرر نوع الكائن المرجع إلى النوع العام لـ Promise.

type SearchFn = (subString: string): Promise<string>;

بدلاً من ذلك يمكنك إعلان نوع عام AsyncFunction.

type AsyncFunction <A,O> = (...args:A) => Promise<O> 
type SearchFn = AsyncFunction<[string], string>

AsyncFunction هو نوع عام يستقبل متغيرين نوعيين - نوع الإدخال (A)، ونوع الإخراج.

الإجابة رقم 4

هذا موضوع معقد. الدالة المميزة بـ async ستُرجع Promise. لكن إرجاع Promise واستخدام async ليسا متكافئين. سأشرح السبب.

على سبيل المثال، في حالتك يمكنك تعريف واجهتك بهذا الشكل:

interface Foo {
    (source: string): Promise<string>;
}

ثم لديك خيارات مختلفة لكتابة دالة تتوافق مع الواجهة:

// complies
const foo1: Foo = (input: string) => {
  return Promise.resolve(input);
};

// complies
const foo2: Foo = async (input: string) => {
  return Promise.resolve(input);
};

// complies: the return value gets wrapped in a Promise
const foo3: Foo = async (input: string) => {
  return input;
};

الآن، واحدة من السلوكيات السحرية لكلمة async هي أن القيمة المرجعة سيتم تغليفها في Promise. على سبيل المثال:

// valid
async function bar1(): Promise<number> {
  return 5;
}

ومن الجدير بالذكر أيضاً أن الوعود تُسوى تلقائياً (ولكن لا أعرف كيف يتم ذلك داخلياً)، لذا فإن الكود التالي صالح:

// valid
async function bar2(): Promise<Promise<number>> {
  return Promise.resolve(5);
}

// also valid... weird
async function bar3(): Promise<Promise<number>> {
  return 5;
}

حتى الآن، لا يوجد فرق جوهري بين استخدام async وPromise، لذا يمكن للمرء أن يجادل بأن async ليست كلمة مفتاحية يجب أن تكون في الواجهات، لأنه يمكنك التعبير عن العقد من حيث الوعود. إنها مجرد سكر نحوي حتى نتمكن من استخدام الكلمة المفتاحية await. مجرد تفصيل تنفيذي، أليس كذلك؟

للأسف. الأمر ليس كذلك. هناك تأثير إضافي واحد لكلمة async يؤثر على العقد وله علاقة بطبيعة الوعود.

انظر إلى الوظيفة التالية:

// supposedly compliant
function nonAsyncFunction(input: number): Promise<void> {
  if (input < 0) {
    throw new TypeError('"input" should be positive');
  }

  return Promise.resolve();
}

غير ضار، أليس كذلك؟ إذا قمت بالتعامل مع الخطأ باستخدام try/catch (باستخدام await في استدعاء الدالة)، فلا توجد مشكلة. ولكن إذا كنت ستتعامل مع الخطأ باستخدام Promise.prototype.catch:

// The function throws before returning a promise, so it's not part of the promise chain
nonAsyncFunction(-1)
  .catch((err) => {
    console.log('caught!');
  });

لن يتم التقاط الخطأ، لأنه عندما تقوم الدالة بإلقاء واستثناء يتم رفعه، بدلاً من إرجاع Promise.

من ناحية أخرى، دالة مثل التالية ستعيد وعدًا مرفوضًا ويمكنك التعامل معه إما باستخدام try/catch أو Promise.prototype.catch:

async function asyncFunction(input: number): Promise<void> {
  if (input < 0) {
    throw new TypeError('"input" should be positive');
  }

  return;
}

جذر المشكلة هو أن لدينا طريقتين للإبلاغ عن الأخطاء، واحدة عن طريق "رفض" الوعود والأخرى عن طريق رفع الأخطاء. ليس لدينا طريقة لفرض أن الدالة التي تُرجع Promise تقوم بالإبلاغ عن الأخطاء باستخدام الوعود فقط أيضًا.

في الختام، async ليس مكافئًا لتعريف نوع الإرجاع على أنه Promise. في الممارسة العملية، استخدام async يضمن أن أي خطأ يحدث سيتم إرجاعه كـ Promise مرفوض، وهو أمر قيم لتطبيقه. للأسف، لا يمكن تعريف دالة async في واجهة.

الإجابة الصحيحة على سؤالك هي أنه لا يمكن القيام بذلك، من الناحية الصارمة.