البناء السلس للوعود الموازية
-
21-12-2019 - |
سؤال
سؤالي يتعلق بالتوازي الأنيق بين الوعود طائر أزرق عندما تحتاج إلى تمرير كل من السياق والوسيطة إلى الوظائف التي تبني الوعود.
ولجعل مشكلتي مفهومة وقابلة للاختبار، قمت بعمل مثال بدون تبعية.
لنفترض أنني أقوم بالحساب ( 1/(xسx) + 1/(x*x)) ) يتضمن "كمبيوتر" غير متزامن (يجب تحرير موارده).يتم حساب المربع والمكعب بشكل غير متزامن ومستقل.
يمكنني إجراء حساباتي مثل هذا:
InitComputer(2) // returns a promise
.then(invert)
.then(function(arg){
return Promise.all([
proto.square(arg),
proto.cube(arg)
]);
}).spread(function(sq, cu){
this.set(sq + cu);
}).catch(function(err){
console.log('err:', err);
}).finally(endComputer);
لكني أجد هذا الاستخدام لـ all
ثقيلة جدًا مقارنة بما هو ممكن نظريًا.عندما تقوم بتمرير دالة كوسيطة إلى then
, ، تم تنفيذه.عندما تقوم بتمرير الوظائف إلى all
, ، إنهم ليسوا كذلك، هناك خطأ.أظن أنني أفتقد أداة مساعدة أو نمطًا ...
هل هناك حل لتغييره إلى شيء أبسط في هذا النمط:
InitComputer(2)
.then(invert)
.all([
proto.square,
proto.cube
]).spread(function(sq, cu){
this.set(sq + cu);
}).catch(function(err){
console.log('err:', err);
}).finally(endComputer);
?
ربما يمكنني الاختراق Promise.prototype.all أو تحديد وظيفة جديدة لتجنب زيادة تعدد الأشكال ولكني مهتم فقط بالحلول التي لا تتضمن تعديل الكائنات التي لا أملكها.
المرفق:
إليك كيفية تعريف "الكمبيوتر" للاختبار الخاص بي:
var Promise = require("bluebird");
function Computer(){}
function InitComputer(v){
// initializing a computer is asynchronous and may fail hence the promise
var c = new Computer(), resolver = Promise.defer();
setTimeout(function(){
if (v>1) resolver.resolve(v);
else resolver.reject(new Error("bad value: "+v));
},100);
return resolver.promise.bind(c);
}
var proto = Computer.prototype;
proto.square = function(x){
// imagine this really uses the computer and is asynchronous
if (!this instanceof Computer) throw new Error('not a computer');
return x*x
}
proto.cube = function(x){ return x*x*x }
proto.set = function(v){ this.value = v }
function endComputer(){
// releases resources here
console.log('value:', this.value);
}
// this asynchronous function doesn't involve or know the computer
function invert(v){ return 1/v }
المحلول
ليس عليك أن تستخدم Promise.all
هناك.بدلاً من القيام بما يلي:
.then(function(arg){
return Promise.all([
proto.square(arg),
proto.cube(arg)
]);
}).spread(...
يمكنك ببساطة استخدام:
.then(function(arg){
return [proto.square(arg), proto.cube(arg)];
}).spread(...
إذا كانت لدينا وظائف أسهم في Node.js، فسيكون الأمر بهذه البساطة:
.then(arg => [proto.square(arg), proto.cube(arg)]).spread(...
Promise.all
يجب استخدامه عندما تحتاج إلى ذلك يبدأ سلسلة من الوعود تتضمن وعودين على الأقل.على سبيل المثال:
var promise1 = somePromise();
var promise2 = somePromise2();
// Start the chain here
Promise.all([promise1, promise2])
.spread(function(value1, value2) {
// ...
});
نصائح أخرى
لإدارة حالة استخدام الموارد كما ذكرت، لدى بلوبيرد Promise.using()
. Promise.using()
يتيح لك الإعداد disposer()
وظيفة لإغلاق المورد الذي تم استرداده بشكل غير متزامن تلقائيًا عند الانتهاء من استخدامه
Promise.join()
من شأنه أن يساعد كذلك على الجمع بين نتائج cube
و square
طرق غير متزامنة
أنا هنا إعادة كتابة قليلا الخاص بك InitComputer
مثال لتوضيح كيفية عمل ذلك - فهو يُرجع الآن ملف Computer
مثيل مع إضافة val كخاصية، بدلاً من val، وقمت بوضعه endComputer
على بروتو أيضا
ملحوظة:يمكنك دائما استخدام Promise.method()
مثل ذلك بدلاً من إرجاع مؤجل:
var invert = Promise.method(function invert(v){ return 1/v })
كمبيوتر أولي جديد:
function InitComputer(v){
var c = new Computer(), resolver = Promise.defer();
setTimeout(function(){
if (v>1) {
c.val = v;
resolver.resolve(c);
}
else resolver.reject(new Error("bad value: "+v));
},100); /** notice resource disposer function added below **/
return resolver.promise.bind(c).disposer(function(compu){compu.endComputer()});
}
رمز جديد:
Promise.using(InitComputer(1.2), function(computer){
return invert(computer.val)
.then(function(inverted){
return Promise.join(computer.square(inverted), computer.cube(inverted),
function(sq, cu){
computer.set(sq + cu)
}
)
})
.catch(function(err){
console.log('err:', err);
});
})