Я использую util.promisify в облачной функции Google для вызова функции преобразования текста в речь IBM Watson, которая возвращает обратный вызов. Мой код работает, но я получаю сообщение об ошибке:
TypeError [ERR_INVALID_ARG_TYPE]: The "original" argument must be of type function
документация говорит
Takes a function following the common error-first callback style, i.e. taking a
(err, value) => ...callback as the last argument, and returns a version that returns promises.
Обратный вызов IBM Watson сложен, и я не могу понять, как преобразовать его в стиль обратного вызова Node.js. Это работает, я должен просто игнорировать сообщение об ошибке? Вот моя облачная функция Google:
exports.IBM_T2S = functions.firestore.document('Users/{userID}/Spanish/IBM_T2S_Request').onUpdate((change) => {
let word = change.after.data().word;
let wordFileType = word + '.mp3';
function getIBMT2S(word, wordFileType) {
const {Storage} = require('@google-cloud/storage');
const storage = new Storage();
const bucket = storage.bucket('myProject.appspot.com');
const file = bucket.file('Audio/Spanish/Latin_America/' + wordFileType);
var util = require('util');
var TextToSpeechV1 = require('watson-developer-cloud/text-to-speech/v1');
var textToSpeech = new TextToSpeechV1({
username: 'groucho',
password: 'swordfish',
url: 'https://stream.watsonplatform.net/text-to-speech/api'
});
var synthesizeParams = {
text: word,
accept: 'audio/mpeg',
voice: 'es-LA_SofiaVoice',
};
const options = { // construct the file to write
metadata: {
contentType: 'audio/mpeg',
metadata: {
source: 'IBM Watson Text-to-Speech',
languageCode: 'es-LA',
gender: 'Female'
}
}
};
textToSpeech.synthesize(synthesizeParams).on('error', function(error) {
console.info(error);
}).pipe(file.createWriteStream(options))
.on('error', function(error) {
console.error(error);
})
.on('finish', function() {
console.info("Audio file written to Storage.");
});
};
var passGetIBMT2S = util.promisify(getIBMT2S(word, wordFileType))
passGetIBMT2S(word, wordFileType)
});



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Это работает, потому что вы вызываете getIBMT2S и передаете возвращаемое значение util.promisfy, а не самой функции.
Здесь есть пара проблем: во-первых, ваша функция getIBMT2S не выглядит совместимой с util.promisfy, как вы подчеркнули в документах, совместимая функция должна следовать типичной подписи в стиле обратного вызова (getIBMT2S не принимает параметр обратного вызова).
Во-вторых, util.promisify ожидает function - в вашем случае вместо этого вы передаете значение функции возвращение. Если бы getIBMT2S был обновлен для поддержки параметра обратного вызова, то правильное использование было бы
function getIBMT2S(word, wordFileType, cb) {
...
// be sure to invoke cb in here
}
var passGetIBMT2S = util.promisify(getIBMT2S); // <-- don't call getIBMT2S, pass it in directly
passGetIBMT2S(word, wordFileType) // <-- now invoke the wrapped function
.then(result => console.info('DONE'));
.catch(e => console.error(e));
Спасибо, это выглядит полезным, я попробую.
@Bergi действительно просто используется в качестве примера для объяснения проблемы.
Спасибо вам обоим! Вместо использования util.promisify я последовал примеру Node в ответе, рекомендованном Берги. Теперь у меня есть два обратных вызова с «новым обещанием» и они работают хорошо.
Вот мой готовый код. Есть две функции. getT2S вызывает IBM Watson Text-to-Speech, затем записывает аудиофайл в хранилище, а затем получает URL-адрес загрузки. writeDownloadURL проверяет, существует ли документ Firestore, а затем либо sets, либо updates URL-адрес загрузки в Firestore.
exports.IBM_T2S = functions.firestore.document('Users/{userID}/Spanish/IBM_T2S_Request').onUpdate((change) => {
if (change.after.data().word != undefined) {
// get requested word object
let accent = change.after.data().accent;
let audioType = change.after.data().audioType;
let gender = change.after.data().gender;
let longLanguage = change.after.data().longLanguage;
let shortLanguage = change.after.data().shortLanguage;
let shortSource = change.after.data().shortSource;
let source = change.after.data().source;
let voice = change.after.data().voice;
let word = change.after.data().word;
console.info(word);
let wordFileType = word + '.' + audioType;
let pronunciation = `${accent}-${gender}-${shortSource}`;
const {Storage} = require('@google-cloud/storage');
const storage = new Storage();
const bucket = storage.bucket('myProject.appspot.com');
const file = bucket.file('Audio/' + longLanguage + '/' + pronunciation + '/' + wordFileType);
var TextToSpeechV1 = require('watson-developer-cloud/text-to-speech/v1');
var textToSpeech = new TextToSpeechV1({
username: 'groucho',
password: 'swordfish',
url: 'https://stream.watsonplatform.net/text-to-speech/api'
});
var synthesizeParams = {
text: word,
accept: 'audio/' + audioType,
voice: voice
};
const options = { // construct the file to write
metadata: {
contentType: 'audio/' + audioType,
metadata: {
accent: accent,
audioType: audioType,
gender: gender,
longLanguage: longLanguage,
shortLanguage: shortLanguage,
source: source,
voice: voice,
word: word
}
}
};
// check if Pronunciations collection exists, set or update to not destroy existing data
function writeDownloadURL(downloadURL) {
admin.firestore().collection('Dictionaries').doc(longLanguage).collection('Words').doc(word).collection('Pronunciations').doc(pronunciation).get()
.then(function(doc) {
if (doc.exists) {
return admin.firestore().collection('Dictionaries').doc(longLanguage).collection('Words').doc(word).collection('Pronunciations').doc(pronunciation).update({ audioFile: downloadURL })
.then(result => console.info('DONE'))
.catch(error => console.error(error));
} else {
return admin.firestore().collection('Dictionaries').doc(longLanguage).collection('Words').doc(word).collection('Pronunciations').doc(pronunciation).set({ audioFile: downloadURL })
.then(result => console.info('DONE'))
.catch(error => console.error(error));
} // close else
})
.catch(error => console.error(error));
} // close writeDownloadURL
// documentation at https://stackoverflow.com/questions/22519784/how-do-i-convert-an-existing-callback-api-to-promises
function getT2S(synthesizeParams) {
return new Promise(function(resolve, reject) {
// documentation at https://cloud.ibm.com/apidocs/text-to-speech?code=node#synthesize-audio-get
textToSpeech.synthesize(synthesizeParams).on('error', function(error) {
console.error(error);
reject(error);
}).pipe(file.createWriteStream(options))
.on('error', function(error) {
console.error(error);
reject(error);
})
.on('finish', function() {
resolve(file.getSignedUrl({
action: 'read',
expires: '03-17-2025'
}));
}); // close on finish
}); // close Promise
} // close getT2SAsync
async function getT2SAsync(synthesizeParams) {
var signedUrls = await getT2S(synthesizeParams);
var downloadURL = signedUrls[0];
await writeDownloadURL(downloadURL);
console.info("All done.");
}
return getT2SAsync(synthesizeParams);
} else { // if no word passed to function
console.error("Error.");
}
}); // close IBM_T2S
я ошибочно написал
return file.getSignedUrl({
вместо
resolve(file.getSignedUrl({
В результате данные из обещания не возвращались, а время ожидания облачной функции истекло через шесть секунд, так и не завершив выполнение. Вы должны что-то сделать с resolve. Я использовал reject дважды, чтобы быть уверенным. :-)
… но если вы можете обновить
getIBMT2S, вы должны просто заставить его вернуть обещание не заставлять его принимать обратный вызов.