рдмрд╛рдЗрдХ: Node.js рдкрд░ рд╡рд╛рджрд╛ рдХрд░рддрд╛ рд╣реИ

рд╢реБрдн рджреЛрдкрд╣рд░, рд╣рдмрд░рд╣рд╛рдмред

рдкреНрд░рд╕реНрддрд╛рд╡рдирд╛


рдпрд╣ рдПрдХ рдХрд╛рдлреА рд╕рд░рд▓ рдХрд╛рд░реНрдп рдерд╛: рдбреЗрдЯрд╛рдмреЗрд╕ рд╕реЗ рджрд╕реНрддрд╛рд╡реЗрдЬреЛрдВ рдХрд╛ рдПрдХ рд╕реЗрдЯ рдкреНрд░рд╛рдкреНрдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рдкреНрд░рддреНрдпреЗрдХ рджрд╕реНрддрд╛рд╡реЗрдЬрд╝ рдХреЛ рдкрд░рд┐рд╡рд░реНрддрд┐рдд рдХрд░реЗрдВ рдФрд░ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдХреЛ рд╕рднреА рдкрд░рд┐рд╡рд░реНрддрд┐рдд рджрд╕реНрддрд╛рд╡реЗрдЬрд╝ рднреЗрдЬреЗрдВ, рдЖрджреЗрд╢ рдХреЛ рдмрджрд▓рд╛ рдирд╣реАрдВ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИ, рджрд╕реНрддрд╛рд╡реЗрдЬрд╝ рдХреЛ рд╕рдВрд╕рд╛рдзрд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдПрдХ рдЕрддреБрд▓реНрдпрдХрд╛рд▓рд┐рдХ рдлрд╝рдВрдХреНрд╢рди рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИред рдпрджрд┐ рдХреБрдЫ рджрд╕реНрддрд╛рд╡реЗрдЬрд╝ рдореЗрдВ рдХреЛрдИ рддреНрд░реБрдЯрд┐ рджрд┐рдЦрд╛рдИ рджреА рд╣реИ - рд╣рдо рджрд╕реНрддрд╛рд╡реЗрдЬрд╝ рдирд╣реАрдВ рднреЗрдЬ рд░рд╣реЗ рд╣реИрдВ, рдХреЗрд╡рд▓ рдПрдХ рддреНрд░реБрдЯрд┐ рд╣реИ рдФрд░ рд╣рдо рджрд╕реНрддрд╛рд╡реЗрдЬрд╝реЛрдВ рдХреЛ рд╕рдВрд╕рд╛рдзрд┐рдд рдХрд░рддреЗ рд╣реИрдВред
рд╕рдорд╕реНрдпрд╛ рдХреЛ рд╣рд▓ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рдХреНрдпреВ рд▓рд╛рдЗрдмреНрд░реЗрд░реА рдХреЛ рдЪреБрдирд╛ рдЧрдпрд╛ рдерд╛, рдХреНрдпреЛрдВрдХрд┐ рдкреНрд░реЛрдорд┐рд╕ рдЕрднрд┐рдпрд╛рди рдореЗрд░реЗ рд▓рд┐рдП рдмрд╣реБрдд рд╕реБрдВрджрд░ рд╣реИред рд▓реЗрдХрд┐рди рдПрдХ рд░реЛрдбрд╝рд╛ рдерд╛, рдпрд╣ рдПрдХ рдкреНрд░рд╛рдердорд┐рдХ рдХрд╛рд░реНрдп рдкреНрд░рддреАрдд рд╣реЛрддрд╛ рд╣реИ, рд▓реЗрдХрд┐рди рдпрд╣ 50-80 рдПрдордПрд╕ рдХреЗ рдмрдЬрд╛рдп рдПрдХ рд╕реЗрдХрдВрдб, рдпрд╛ 1300 рдПрдордПрд╕ рд╕реЗ рдЕрдзрд┐рдХ рд▓реЗрддрд╛ рд╣реИред рдпрд╣ рд╕рдордЭрдиреЗ рдХреЗ рд▓рд┐рдП рдХрд┐ рд╕рдм рдХреБрдЫ рдХреИрд╕реЗ рд╡реНрдпрд╡рд╕реНрдерд┐рдд рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ рдФрд░ рдЕрддреБрд▓реНрдпрдХрд╛рд▓рд┐рдХ рдХреЗ рд╕рд╛рде imbued рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ, рдЗрд╕ рдХрд╛рд░реНрдп рдХреЗ рд▓рд┐рдП рдПрдХ рд╡рд┐рд╢реЗрд╖ "рд╕рд╛рдЗрдХрд┐рд▓" рд▓рд┐рдЦрдиреЗ рдХрд╛ рдлреИрд╕рд▓рд╛ рдХрд┐рдпрд╛ рдЧрдпрд╛ рдерд╛ред



рдХреНрдпреВ рдХреЗ рд╕рд╛рде рдХрд╛рдо рдХреИрд╕реЗ рд╡реНрдпрд╡рд╕реНрдерд┐рдд рдХрд┐рдпрд╛ рдЧрдпрд╛ рдерд╛?



рд╕рдмрд╕реЗ рдкрд╣рд▓реЗ, рдореИрдВ рдЖрдкрдХреЛ рдпрд╣ рдмрддрд╛рдирд╛ рдЪрд╛рд╣реВрдВрдЧрд╛ рдХрд┐ рдЗрд╕реЗ рд╢реБрд░реВ рдореЗрдВ рдХреИрд╕реЗ рд▓рд╛рдЧреВ рдХрд┐рдпрд╛ рдЧрдпрд╛ рдерд╛ред

1. рдПрдХ рдкреНрд░рдХреНрд░рд┐рдпрд╛ рдЬреЛ рдХреНрд░рдорд┐рдХ рд░реВрдк рд╕реЗ рд╕рд░рдгреА рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗ рдЪрд▓реА рдЧрдИ рдФрд░ рд╣рдореЗрдВ рдПрдХ рд╡рд╛рджрд╛ рд▓реМрдЯрд╛рдпрд╛ред
forEachSerial
function forEachSerial(array, func) { var tickFunction = function (el, index, callback) { if (func.length == 2) func(el, callback); else if (func.length == 3) func(el, index, callback); } var functions = []; array.forEach(function (el, index) { functions.push(Q.nfcall(tickFunction, el, index)); }); return Q.all(functions); } 



2. рд╡рд╣ рдкреНрд░рдХреНрд░рд┐рдпрд╛ рдЬрд┐рд╕рдиреЗ рджрд╕реНрддрд╛рд╡реЗрдЬрд╝ рдХреЛ рд╕рдВрд╕рд╛рдзрд┐рдд рдХрд┐рдпрд╛ рдФрд░ рд╣рдореЗрдВ рдПрдХ рд╡рд╛рджрд╛ рд╡рд╛рдкрд╕ рдХрд┐рдпрд╛ред
documentToJSON
 function documentToJSON(el, options) { var obj = el.toObject(options); var deferred = Q.defer(); var columns = ['...','...','...']; forEachSerial(columns,function (el, next) { el.somyAsyncFunction(options, function (err, result) { if (err) next(err); else result.somyAsyncFunction2(options, function (err, result) { if (!err) obj[el] = result; next(err); }); }); }).then(function () { deferred.resolve(obj); }, function (err) { deferred.reject(err); }); return deferred.promise; } 



3. рдФрд░ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдХреЛ рдкрд░рд┐рдгрд╛рдо рднреЗрдЬрдиреЗ рдХреА рдкреНрд░рдореБрдЦ рдкреНрд░рдХреНрд░рд┐рдпрд╛
sendResponse
 exports.get = function (req, res, next) { dbModel.find(search, {}, { skip: from, limit: limit }).sort({column1: -1}).exec(function (err, result) { if (err) next(new errorsHandlers.internalError()); else { var result = []; forEachSerial(result,function (el, index, next) { documentToJSON(el,options).then(function (obj) { result[index] = obj; next() }, function (err) { next(new errorsHandlers.internalError()) }); }).then(function () { res.send({responce: result}); }, function (err) { next(new errorsHandlers.internalError()) }); } }); }; 



рдХреЛрдИ рдпрд╣ рджреЗрдЦ рд╕рдХрддрд╛ рд╣реИ рдХрд┐ рд╕рднреА рдкреНрд░рдХрд╛рд░ рдХреЗ "рд╡рд╛рджреЗ" рдмрд╣реБрдд рд╕рд╛рд░реЗ рд╣реИрдВ, рдпрд╣рд╛рдВ рддрдХ тАЛтАЛрдХрд┐ рдЬрд╣рд╛рдВ рд╡реЗ рдмрд╣реБрдд рдЖрд╡рд╢реНрдпрдХ рдирд╣реАрдВ рд╣реИрдВ, рдФрд░ рдпрд╣ рдХрд┐ рдЗрд╕ рддрд░рд╣ рдХреЗ рдПрдХ рдмрдбрд╝реЗ рд▓реВрдк рдиреЗ рдРрд╕реА рдЧрддрд┐ рдкреИрджрд╛ рдХреА, рд▓реЗрдХрд┐рди рдкреНрд░рдХреНрд░рд┐рдпрд╛ рдХреЗрд╡рд▓ 20 рд╕рд░рд▓ рджрд╕реНрддрд╛рд╡реЗрдЬ рджреЗрддреА рд╣реИ, рдкрд░рд┐рд╡рд░реНрддрди рдЖрджрд┐рдо рд╣реИрдВ рдФрд░ рдпрд╣ рдЙрдиреНрд╣реЗрдВ рдЗрддрдирд╛ рд╕рдордп рдкреВрд░рд╛ рдХрд░рдирд╛ рд╕реБрдирд┐рд╢реНрдЪрд┐рдд рдХрд░рддрд╛ рд╣реИ рдЕрдЪреНрдЫрд╛ рдирд╣реАрдВред

рд╣рдо рдЕрдкрдирд╛ рд╡рд╛рджрд╛ рдкреБрд╕реНрддрдХрд╛рд▓рдп рд▓рд┐рдЦрддреЗ рд╣реИрдВ


рдпрд╣ рдХреИрд╕реЗ рдХрд╛рдо рдХрд░рддрд╛ рд╣реИ?

рдиреЗрдЯрд╡рд░реНрдХ рдХреНрдпрд╛ рдФрд░ рдХреИрд╕реЗ рдХреЗ рд╡рд┐рд╡рд░рдгреЛрдВ рд╕реЗ рднрд░рд╛ рд╣реИред рдореИрдВ рд╕рдВрдХреНрд╖реЗрдк рдореЗрдВ рд╡рд░реНрдгрди рдХрд░реВрдВрдЧрд╛ред рд╡рд╛рджрд╛ рдПрдХ рддрд░рд╣ рдХрд╛ рд╡рд╛рджрд╛ рд╣реИред рдХреБрдЫ рдлрд╝рдВрдХреНрд╢рди рд╣рдореЗрдВ рдкрд░рд┐рдгрд╛рдо рдХрд╛ рд╡рд╛рджрд╛ рдХрд░рддреЗ рд╣реИрдВ, рд╣рдо рдЗрд╕реЗ рддрдм (рд╕рдлрд▓рддрд╛, рддреНрд░реБрдЯрд┐) рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рдкреНрд░рд╛рдкреНрдд рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ, рдмрджрд▓реЗ рдореЗрдВ, рдпрджрд┐ рдкреНрд░рд╕рдВрд╕реНрдХрд░рдг рд╕рдлрд▓ рд╣реЛрддрд╛ рд╣реИ, рддреЛ рд╣рдо рдПрдХ рдирдпрд╛ рд╡рд╛рджрд╛ рд╕реМрдВрдк рд╕рдХрддреЗ рд╣реИрдВ рдФрд░ рдЗрд╕реЗ рднреА рд╕рдВрд╕рд╛рдзрд┐рдд рдХрд░ рд╕рдХрддреЗ рд╣реИрдВред рдХрд┐рд╕реА рд╡рд┐рд╢реЗрд╖ рдорд╛рдорд▓реЗ рдореЗрдВ, рдРрд╕рд╛ рджрд┐рдЦрддрд╛ рд╣реИ:
 Promise.then(step1).then(step2).then(step3).then(function () { //All OK }, function (err) { //Error in any step }); 

рдкреНрд░рддреНрдпреЗрдХ рдЪрд░рдг рдХрд╛ рдкрд░рд┐рдгрд╛рдо рдПрдХ рдкреИрд░рд╛рдореАрдЯрд░ рдХреЗ рд░реВрдк рдореЗрдВ рдЕрдЧрд▓реЗ рдФрд░ рдЗрддрдиреЗ рдкрд░ рдХреНрд░рдорд┐рдХ рд░реВрдк рд╕реЗ рдкрд╛рд░рд┐рдд рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИред рдирддреАрдЬрддрди, рд╣рдо рдПрдХ рдмреНрд▓реЙрдХ рдореЗрдВ рд╕рднреА рддреНрд░реБрдЯрд┐рдпреЛрдВ рдХреЛ рд╕рдВрд╕рд╛рдзрд┐рдд рдХрд░рддреЗ рд╣реИрдВ рдФрд░ "рдиреВрдбрд▓реНрд╕" рд╕реЗ рдЫреБрдЯрдХрд╛рд░рд╛ рдкрд╛ рд▓реЗрддреЗ рд╣реИрдВред
рдЕрдВрджрд░, рдпрд╣ рдХреБрдЫ рдЗрд╕ рддрд░рд╣ рджрд┐рдЦрддрд╛ рд╣реИ: рдШрдЯрдирд╛рдПрдВ рдЙрддреНрдкрдиреНрди рд╣реЛрддреА рд╣реИрдВ рдЬреЛ рд╕рдлрд▓рддрд╛ рдпрд╛ рддреНрд░реБрдЯрд┐ рдкрд░ рдЙрдард╛рдИ рдЬрд╛рддреА рд╣реИрдВ:
 var promise = fs.stat("foo"); promise.addListener("success", function (value) { // ok }) promise.addListener("error", function (error) { // error }); 

рдпрд╣ рд╕рдм рдпрд╣рд╛рдБ рдкрдврд╝рд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИ ред
рд╕рд┐рджреНрдзрд╛рдВрдд рдЦрддреНрдо рд╣реЛ рдЧрдпрд╛ рд╣реИ, рдЪрд▓реЛ рдЕрднреНрдпрд╛рд╕ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдиреАрдЪреЗ рдЙрддрд░реЗрдВред

рдЪрд▓реЛ рд╕рд░рд▓ рдХреЗ рд╕рд╛рде рд╢реБрд░реВ рдХрд░рддреЗ рд╣реИрдВ - рдЖрд╕реНрдердЧрд┐рдд

рдЗрд╕ рдСрдмреНрдЬреЗрдХреНрдЯ рдХрд╛ рдЙрджреНрджреЗрд╢реНрдп рдЙрди рдШрдЯрдирд╛рдУрдВ рдХреЛ рдмрдирд╛рдирд╛ рд╣реЛрдЧрд╛ рдЬрд┐рдирдХреА рд╣рдореЗрдВ рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ рдФрд░ рд╡рд╛рджрд╛ рдЬрд╛рд░реА рдХрд░реЗрдВ
 function deferred() { this.events = new EventEmitter(); //  this.promise = new promise(this); //   Promise this.thenDeferred = []; // ,           var self = this; //    this.resolve = function () { self.events.emit('completed', arguments); } //    this.reject = function (error) { self.events.emit('error', error); //     self.thenDeferred.forEach(function (el) { el.reject(error); }); } } 

рд╡рд╕реНрддреБ - рд╡рд╛рджрд╛

рдЗрд╕рдХрд╛ рдХрд╛рд░реНрдп "рддрдм" рдФрд░ " рддреНрд░реБрдЯрд┐ " рдХреА рдШрдЯрдирд╛рдУрдВ рдХреА рдирд┐рдЧрд░рд╛рдиреА рдХрд░рдирд╛ рд╣реЛрдЧрд╛ рдЬреЛ " рддрдм " рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗ рдЕрд╕рд╛рдЗрди рдХрд┐рдП рдЧрдП рдЖрд╡рд╢реНрдпрдХ рдХрд╛рд░реНрдпреЛрдВ рдХреЛ рдХреЙрд▓ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдФрд░ рдпрд╣ рдЯреНрд░реИрдХ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдХрд┐ рдпрд╣ рдлрд╝рдВрдХреНрд╢рди рд╡рд╣рд╛рдВ рдХреНрдпрд╛ рд▓реМрдЯрд╛ рд╣реИ: рдпрджрд┐ рдпрд╣ рд╣рдорд╛рд░реЗ рд▓рд┐рдП рдПрдХ рдФрд░ рд╡рд╛рджрд╛ рд▓реМрдЯрд╛рддрд╛ рд╣реИ, рддреЛ рдЗрд╕реЗ рдХрдиреЗрдХреНрдЯ рдХрд░реЗрдВ рддрд╛рдХрд┐ рдЕрдЧрд▓рд╛ рддрдм рдХрд╛рдо рдХрд░реЗ, рдЕрдЧрд░ рдмрд╕ рдбреЗрдЯрд╛, рдлрд┐рд░ рдмрд╛рдж рдореЗрдВ рдирд┐рд╖реНрдкрд╛рджрд┐рдд рдХрд░реЗрдВ, рдЗрд╕рд▓рд┐рдП рд╣рдо рддрдм рд╕реЗ рдЪреЗрди рдмрдирд╛ рд╕рдХрддреЗ рд╣реИрдВред
 function promise(def) { this.def = def; this.completed = false; this.events = def.events; var self = this; var thenDeferred; self._successListener = null; self._errorListener = null; //  then -    promise this.then = function (success, error) { if (success) self._successListener = success; if (error) self._errorListener = error; thenDeferred = new deferred(); self.def.thenDeferred.push(thenDeferred); return thenDeferred.promise; } //    this.events.on('completed', function (result) { // , ,               var args = inputOfFunctionToArray(result); //     ,     if (self.completed) return; self.completed = true; if (self._successListener) { var result; try { result = self._successListener.apply(self, args); } catch (e) { self.def.reject(e); result; } //   Promise    then,    var promise; if (isPromise(result)) promise = result; else if (result instanceof deferred) promise = result.promise; if (promise && thenDeferred) { promise.then(function () { var args = arguments; process.nextTick(function () { thenDeferred.resolve.apply(self, args); }); }, function (error) { process.nextTick(function () { thenDeferred.reject(error); }); }); } else if (thenDeferred) process.nextTick(function () { //      then thenDeferred.resolve.apply(self, [result]); }); } else if (thenDeferred) process.nextTick(function () { thenDeferred.resolve.apply(self, []); }); }); //  this.events.on('error', function (error) { if (self.completed) return; self.completed = true; if (self._errorListener) process.nextTick(function () { self._errorListener.apply(self, [error]); }); }); } 


рддреЛ, рдореВрд▓ рдореЙрдбрд▓ рддреИрдпрд╛рд░ рд╣реИред рдпрд╣ рдХреЙрд▓рдмреИрдХ рдХреЗ рд╕рд╛рде рдХрд╛рд░реНрдпреЛрдВ рдХреЗ рд▓рд┐рдП рдПрдХ рдмрдВрдзрди рдмрдирд╛рдиреЗ рдХреЗ рд▓рд┐рдП рдмрдиреА рд╣реБрдИ рд╣реИ

PromiseFn

рдЗрд╕рдХрд╛ рдХрд╛рд░реНрдп рдХреЙрд▓рдмреИрдХ рдХреЗ рд╕рд╛рде рдПрдХ рдлрд╝рдВрдХреНрд╢рди рдХреЗ рд▓рд┐рдП рдПрдХ рдЖрд╡рд░рдг рдмрдирд╛рдирд╛ рд╣реИ рдЬреЛ рдЗрд╕реЗ рдирд┐рд░реНрджрд┐рд╖реНрдЯ рдХрд░рдиреЗ рдФрд░ рддрд░реНрдХ рд╢реБрд░реВ рдХрд░рдиреЗ рдХреА рдХреНрд╖рдорддрд╛ рдХреЗ рд╕рд╛рде рд╣реИ
 var promisefn = function (bind, fn) { var def = new deferred(); //bind     if (typeof bind === 'function' && !fn) { fn = bind; bind = def; } //  callback    var callback = function (err) { if (err) def.reject(err); else { var args = []; for (var key in arguments) args.push(arguments[key]); args.splice(0, 1); def.resolve.apply(bind, args); } }; var result = function () { var args = []; for (var key in arguments) args.push(arguments[key]); args.push(callback); process.nextTick(function () { fn.apply(bind, args); }); return def.promise; } return result; } 


рдФрд░ рдЕрдВрдд рдореЗрдВ рд╕рднреА рдХреЙрд▓рдмреИрдХ рдХреЗ рд╕рд╛рде рдХрд╛рд░реНрдпреЛрдВ рдХрд╛ рдЕрдиреБрдХреНрд░рдорд┐рдХ рдирд┐рд╖реНрдкрд╛рджрди

рдпрд╣рд╛рдВ рд╕рдм рдХреБрдЫ рд╕рд░рд▓ рд╣реИ: рдлрд╝рдВрдХреНрд╢рди рдХрд╛ рдПрдХ рд╕рд░рдгреА рд╣рдорд╛рд░реЗ рдкрд╛рд╕ рд╣реИ, рд╣рдо рдЙрдиреНрд╣реЗрдВ рд╡рд╛рджреЗ рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗ рдмрд╛рдВрдзрддреЗ рд╣реИрдВ, рдФрд░ рдЬрдм рд╡реЗ рд╕рднреА рдкреВрд░реЗ рд╣реЛ рдЬрд╛рддреЗ рд╣реИрдВ, рддреЛ рд╣рдо рд╕рдВрдХрд▓реНрдк рдХрд╣рддреЗ рд╣реИрдВ
 var all = function (functions) { var def = new deferred(); process.nextTick(function () { var index = -1; var result = []; var next = function (err, arguments) { if (err) { def.reject(err); return; } if (arguments) result.push(inputOfFunctionToArray(arguments)); index++; if (index >= functions.length) { def.resolve(result); } else process.nextTick(function () { promisefn(functions[index])().then(function () { var args = arguments; process.nextTick(function () { next(err, args); }); }, function (err) { process.nextTick(function () { next(err); }); }); }); } process.nextTick(next); }); return def.promise; } 


рдирд┐рд╖реНрдХрд░реНрд╖ рдореЗрдВ


рдкрд░реАрдХреНрд╖рдг рдХреЗ рдмрд╛рдж, рдкреБрд░рд╛рдиреЗ рджреГрд╖реНрдЯрд┐рдХреЛрдг (рдХреНрдпреВ рд▓рд╛рдЗрдмреНрд░реЗрд░реА рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗ) рдХреЛ рдлрд┐рд░ рд╕реЗ рд▓рд┐рдЦрд╛ рдЧрдпрд╛, рдХреБрдЫ рд╡рд┐рдЬреНрдЮрд╛рдкрдиреЛрдВ рджреНрд╡рд╛рд░рд╛ рдкреНрд░рддрд┐рд╕реНрдерд╛рдкрд┐рдд рдХрд┐рдпрд╛ рдЧрдпрд╛ рдФрд░ рдЙрдиреНрд╣реАрдВ рд╢рд░реНрддреЛрдВ рдХреЗ рддрд╣рдд рд▓реЙрдиреНрдЪ рдХрд┐рдпрд╛ рдЧрдпрд╛ред рдкрд░рд┐рдгрд╛рдо рд╕рдХрд╛рд░рд╛рддреНрдордХ рд╣реИ - 50-100 рдПрдордПрд╕ (рдкрд┐рдЫрд▓реЗ 1300 рдПрдордПрд╕ рдХреЗ рдмрдЬрд╛рдп)ред
рдЬреАрдердм рдкрд░ рд╕рднреА рд╕реНрд░реЛрдд рдЙрдкрд▓рдмреНрдз рд╣реИрдВ, рд╡рд╣рд╛рдВ рдЖрдк рдЙрджрд╛рд╣рд░рдг рднреА рдкрд╛ рд╕рдХрддреЗ рд╣реИрдВред "рд╕рд╛рдЗрдХрд┐рд▓" рдХрд╛ рдЖрд╡рд┐рд╖реНрдХрд╛рд░ рдХрдо рд╕реЗ рдХрдо рдЙрдкрдпреЛрдЧреА рд╣реИ рдХрд┐ рдпрд╣ рд╕рдордЭ рдХреЛ рдмреЗрд╣рддрд░ рдмрдирд╛рддрд╛ рд╣реИред
рдЖрдкрдХрд╛ рдзреНрдпрд╛рди рдХреЗ рд▓рд┐рдП рдзрдиреНрдпрд╡рд╛рдж!

Source: https://habr.com/ru/post/In168929/


All Articles