Въведение
В тази статия ще научите как да пишете собствената си функция за обещаване от нулата.
Promisification помага при справяне с API-та, базирани на обратно повикване, като същевременно поддържа кода в съответствие с обещанията.
Можем просто да обгърнем всяка функция new Promise()
и изобщо да не се тревожим за нея. Но това, когато имаме много функции, би било излишно.
Ако разбирате обещания и обратно извикване, тогава научаването как да пишете обещаващи функции трябва да е лесно. Така че нека да започнем.
Но замисляли ли сте се как работи promisify?
Важното е да не спирате да разпитвате. Любопитството има своя причина за съществуване.- Алберт Айнщайн
Обещанията бяха въведени в стандарта ECMA-262, 6-то издание (ES6), публикувано през юни 2015 г.
Това беше доста подобрение спрямо обратните обаждания, тъй като всички знаем колко нечетлив може да бъде „адът на обратно обаждане“ :)

Като разработчик на Node.js трябва да знаете какво е обещание и как работи вътрешно, което също ще ви помогне в JS интервюта. Чувствайте се свободни да ги прегледате бързо, преди да прочетете.
Защо трябва да конвертираме обратно извикване в обещания?
- С обратните обаждания, ако искате да направите нещо последователно, ще трябва да посочите
err
аргумент във всяко обратно извикване, което е излишно. В обещания или async-await можете просто да добавите.catch
метод или блок, който да улови всички грешки, възникнали във веригата за обещания - С обратните обаждания нямате контрол кога е извикан, под какъв контекст или колко пъти е извикан, което може да доведе до изтичане на памет.
- Използвайки обещания, ние контролираме тези фактори (особено обработката на грешки), така че кодът е по-четлив и поддържаем.
Как да накарам функциите, базирани на обратно извикване, да връщат обещание
Има два начина да го направите:
- Опаковайте функцията в друга функция, която връща обещание. След това разрешава или отхвърля въз основа на аргументи за обратно извикване.
- Promisification - Ние създаваме util / helper функция,
promisify
която ще преобразува всички API-та, базирани на обратни повиквания при грешки.
Пример: има API, базиран на обратно повикване, който предоставя сумата от две числа. Искаме да го обещаем, така че да върне thenable
обещание.
const getSumAsync = (num1, num2, callback) => { if (!num1 || !num2) { return callback(new Error("Missing arguments"), null); } return callback(null, num1 + num2); } getSumAsync(1, 1, (err, result) => { if (err){ doSomethingWithError(err) }else { console.log(result) // 2 } })
Увийте в обещание
Както можете да видите, getSumPromise
делегира цялата работа на оригиналната функция getSumAsync
, предоставяйки свой собствен обратен разговор, който се превежда като обещание resolve/reject
.
Promisify
Когато трябва да обещаем много функции, можем да създадем помощна функция promisify
.
Какво е промисификация?
Промисификацията означава трансформация. Това е преобразуване на функция, която приема обратно извикване във функция, връщаща обещание.
Използване на Node.js util.promisify()
:
const { promisify } = require('util') const getSumPromise = promisify(getSumAsync) // step 1 getSumPromise(1, 1) // step 2 .then(result => { console.log(result) }) .catch(err =>{ doSomethingWithError(err); })
Така изглежда магическа функция, която се трансформира getSumAsync
в getSumPromise
която има .then
и .catch
методи
Нека напишем нашата собствена функция promisify:
Ако погледнете стъпка 1 в горния код, promisify
функцията приема функция като аргумент, така че първото нещо, което трябва да направим, е да напишем функция, която може да направи същото:
const getSumPromise = myPromisify(getSumAsync) const myPromisify = (fn) => {}
След това getSumPromise(1, 1)
е извикване на функция. Това означава, че нашата promisify трябва да върне друга функция, която може да бъде извикана със същите аргументи на оригиналната функция:
const myPromisify = (fn) => { return (...args) => { } }
В горния код можете да видите, че ние разпространяваме аргументи, защото не знаем колко аргументи има оригиналната функция. args
ще бъде масив, съдържащ всички аргументи.
Когато се обаждате getSumPromise(1, 1)
, всъщност се обаждате (...args)=> {}
. В изпълнението по-горе връща обещание. Ето защо можете да използвате getSumPromise(1, 1).then(..).catch(..)
.
Надявам се, че сте получили намека, че функцията обвивка (...args) => {}
трябва да върне обещание.
Върнете обещание
const myPromisify = (fn) => { return (...args) => { return new Promise((resolve, reject) => { }) } }
Сега сложната част е как да решите кога да resolve or reject
обещаете.
Всъщност това ще бъде решено от изпълнението на оригиналната getSumAsync
функция - тя ще извика оригиналната функция за обратно извикване и ние просто трябва да я дефинираме. Тогава въз основа на err
и result
ние ще reject
или resolve
обещанието.
const myPromisify = (fn) => { return (...args) => { return new Promise((resolve, reject) => { function customCallback(err, result) { if (err) { reject(err) }else { resolve(result); } } }) } }
Нашата args[]
само се състои от аргументи, предадени от, getSumPromise(1, 1)
с изключение на функцията за обратно извикване. Така че трябва да добавите customCallback(err, result)
към това, args[]
което оригиналната функция getSumAsync
ще извика съответно, тъй като проследяваме резултата customCallback
.
Натиснете customCallback към аргументи []
const myPromisify = (fn) => { return (...args) => { return new Promise((resolve, reject) => { function customCallback(err, result) { if (err) { reject(err) }else { resolve(result); } } args.push(customCallback) fn.call(this, ...args) }) } }
Както можете да видите, ние добавихме fn.call(this, args)
, което ще извика оригиналната функция в същия контекст с аргументите getSumAsync(1, 1, customCallback)
. Тогава нашата обещаваща функция трябва да бъде в състояние да resolve/reject
съответно.
The above implementation will work when the original function expects a callback with two arguments, (err, result)
. That’s what we encounter most often. Then our custom callback is in exactly the right format and promisify
works great for such a case.
But what if the original fn
expects a callback with more arguments likecallback(err, result1, result2, ...)
?
In order to make it compatible with that, we need to modify our myPromisify
function which will be an advanced version.
const myPromisify = (fn) => { return (...args) => { return new Promise((resolve, reject) => { function customCallback(err, ...results) { if (err) { return reject(err) } return resolve(results.length === 1 ? results[0] : results) } args.push(customCallback) fn.call(this, ...args) }) } }
Example:
const getSumAsync = (num1, num2, callback) => { if (!num1 || !num2) { return callback(new Error("Missing dependencies"), null); } const sum = num1 + num2; const message = `Sum is ${sum}` return callback(null, sum, message); } const getSumPromise = myPromisify(getSumAsync) getSumPromise(2, 3).then(arrayOfResults) // [6, 'Sum is 6']
That’s all! Thank you for making it this far!
I hope you’re able to grasp the concept. Try to re-read it again. It’s a bit of code to wrap your head around, but not too complex. Let me know if it was helpful ?
Don’t forget to share it with your friends who are starting with Node.js or need to level up their Node.js skills.
References:
//nodejs.org/dist/latest-v8.x/docs/api/util.html#util_util_promisify_original
//github.com/digitaldesignlabs/es6-promisify
You can read other articles like this at 101node.io.