#javascript #promise
#javascript #обещание
Вопрос:
Мне нужно изменить существующий код для поддержки синхронных и асинхронных результатов. Хотя я могу легко обрабатывать Task
и await
на C #, даже после большого чтения MDN и других страниц я просто не могу разобраться в JavaScript Promise
.
Существующий код выглядит следующим образом:
function dispatchCall() {
// ...
try {
let result = fn.apply(context, args);
if (result !== undefined) {
return { status: 0, result: result };
}
return { status: 0 };
}
catch (err) {
if (typeof err === "object") {
return { status: 400, errorMessage: err.name ", " err.message, stack: err.stack };
}
return { status: 400, errorMessage: err };
}
}
fn
это функция, которая должна быть вызвана. Это определяется пользователем моего API, поэтому я не знаю, что он будет делать. На данный момент он всегда будет возвращать значение или выдавать исключение. Это значение будет заключено в объект message, который передается обратно удаленному вызывающему dispatchCall
.
Теперь fn
необходимо вернуть Promise
, потому что оно должно использоваться в асинхронном рабочем процессе, где результат не доступен немедленно.
Мне нужно проверить, является ли result
это Promise
(или «thenable») и действовать соответствующим образом. В этом случае, когда обещание результата будет выполнено, мне нужно обернуть результирующее значение в соответствующий объект message и передать это как другое обещание вызывающему dispatchCall
. Затем я могу легко с этим справиться.
Я не могу решить проблему «передачи и изменения значения».
Вот как я бы начал:
function dispatchCall() {
// ...
try {
let result = fn.apply(context, args);
// --------------------------------------------------
if (result amp;amp; typeof result.then === "function") {
result.then(function (result) {
// Like so?
if (result !== undefined) {
return { status: 0, result: result };
}
return { status: 0 };
})
.catch(function (err) {
// Does this catch errors?
if (typeof err === "object") {
return { status: 400, errorMessage: err.name ", " err.message, stack: err.stack };
}
return { status: 400, errorMessage: err };
});
return new Promise(function(resolve, reject) {
// What about this?
// When should I call resolve and reject and with what arguments?
});
// Must return a Promise and not continue at this point!
}
// --------------------------------------------------
if (result !== undefined) {
return { status: 0, result: result };
}
return { status: 0 };
}
catch (err) {
if (typeof err === "object") {
return { status: 400, errorMessage: err.name ", " err.message, stack: err.stack };
}
return { status: 400, errorMessage: err };
}
}
Как это должно быть склеено вместе?
Просмотрев таблицы поддержки, я решил отказаться от поддержки Internet Explorer и использовать обещания ES6. Никакая внешняя библиотека не задействована. В случае, если какой-либо IE выполнит это, он должен продолжать работать с синхронными функциями и может с треском провалиться с асинхронным кодом.
Моими целевыми средами являются браузеры и Node.js .
Комментарии:
1. Вам действительно нужно возвращать этот объект синхронно, когда
fn()
не было возвращено обещание? Было бы намного проще, если бы ваша функция всегда могла возвращать обещание.2. @Bergi Да, потому что я не хочу использовать
Promise
без необходимости.3. @ygoe нужно изменить свой образ мышления. Если возможность возврата promise в любой его части, то следует возвращать promise при любых условиях, и ваш вызов
dispatchCall()
всегда будет иметьthen()
илиasync/await
Ответ №1:
Нет необходимости создавать обещание с new Promise
, когда у вас есть thenable.
Вы можете просто вернуть return result.then(......
, или если вы хотите быть уверены, что верните экземпляр Promise (а не только то, что это then().catch()
возвращает), затем передайте результат в: Promise.resolve()
if (typeof result.then === "function") {
return Promise.resolve(result).then(function (result) {
if (result !== undefined) {
return { status: 0, result: result };
}
return { status: 0 };
})
.catch(function (err) {
if (typeof err === "object") {
return { status: 400, errorMessage: err.name ", " err.message, stack: err.stack };
}
return { status: 400, errorMessage: err };
});
}
Если при catch
обратном вызове возникает ошибка, то возвращенное обещание будет разрешено как отклоненное. Вызывающий может обработать это с помощью своего собственного catch
, привязанного к нему.
Комментарии:
1. Вы бы обернули только
result
вPromise.resolve()
вызов, а не результат вызова метода (и даже цепочкиcatch
) в thenable.
Ответ №2:
Поскольку результат может быть получен асинхронно, у потребителя dispatchCall
должна быть логика для ожидания возврата возможно асинхронных данных. Одним из вариантов было бы dispatchCall
вернуть обещание, которое в конечном итоге преобразуется в { status, result }
объект (или { status, error }
object), который вы ищете:
if (typeof result.then === "function") {
return result
.then((resolveValue) => {
return { status: 0, result: resolveValue };
})
.catch((error) => {
return { status: 400, error };
})
}
И в получателе dispatchCall
проверьте, является ли возвращаемое значение обещанием — если это так, вызовите .then
для него:
const dispatchResult = dispatchCall();
if (typeof dispatchResult.then === 'function') {
dispatchResult.then(({ status, result, error }) => {
// do stuff with status, result, error here
// if there was an error, result will be undefined
});
} else {
// do stuff with dispatchResult.status, .result, .errorMessage
}
Вы также могли бы рассмотреть возможность возврата обещания, независимо от того, result
является ли это обещанием или нет, чтобы упростить управление кодом — например, в синхронном разделе:
return Promise.resolve({ status: 0, result: result });
Ответ №3:
Вам действительно нужно возвращать этот объект синхронно, когда fn()
не было возвращено обещание? Было бы намного проще, если бы ваша функция всегда могла возвращать обещание. Вы бы просто resolve
использовали значение, и независимо от того, было ли это простое значение, thenable или promise, вы получите от него обещание в ответ.
Затем вы бы привязали свою логику обработки результатов / ошибок к этому, только один раз:
function dispatchCall() {
// ...
// return Promise.resolve(fn.apply(context, args))… - doesn't catch synchronous exceptions
return new Promise(function(resolve) {
resolve(fn.apply(context, args)); // promise constructor catches exceptions
}).then(function (result) {
// the promise was fulfilled
if (result !== undefined) {
return { status: 0, result: result };
} else {
return { status: 0 };
}
}, function (err) {
// the promise was rejected
if (typeof err === "object") {
return { status: 400, errorMessage: err.name ", " err.message, stack: err.stack };
} else {
return { status: 400, errorMessage: err };
}
});
}
Комментарии:
1. Да, потому что я не хочу использовать
Promise
без необходимости. Также, что очень важно,fn
может вообще ничего не возвращать, и в этом случае я должен отправить сообщение со статусом 0, но без результата. Это для функций void.2. @ygoe Если
fn
вообще ничего не возвращает, объект будет иметьresult
свойствоundefined
со значением. Сериализация JSON полностью устранит это. Я сомневаюсь, что важно полностью опускать свойство.3. @ygoe Что вы подразумеваете под «без необходимости»? Обещания дешевы, и их использование значительно упрощает обработку. Существует ли реальная потребность в синхронном результате?
4. Я просмотрел это, и нет реальной необходимости в синхронном результате от
dispatchCall
. Но, как я уже сказал, я хотел бы поддерживать поддержку IE для тех сценариев, которые могут быть выполнены. Если пользователь моей библиотеки решит добавить какую-нибудь стороннюю библиотеку для promises, и моя библиотека может просто повторно использовать это, это нормально, но я не хочу вводить такую зависимость без необходимости, т. Е. пользователь хочет асинхронный код.5. @ygoe Если это выбор, то вам, вероятно, лучше отправить две версии вашей библиотеки, одну синхронную, а другую использующую promises. Иметь две кодовые базы (или, возможно, просто параметры сборки) будет проще, чем смешивать логику синхронизации и асинхронности в одном коде и иметь необязательную зависимость от promises.