สำเร็จ: / ล้มเหลว: บล็อก vs เสร็จสมบูรณ์: บล็อก


23

ฉันเห็นรูปแบบทั่วไปสองแบบสำหรับบล็อกใน Objective-C หนึ่งคือคู่ของความสำเร็จ: / ความล้มเหลว: บล็อกอื่น ๆ คือความสมบูรณ์เดียว: บล็อก

ตัวอย่างเช่นสมมติว่าฉันมีงานที่จะส่งคืนวัตถุแบบอะซิงโครนัสและงานนั้นอาจล้มเหลว -taskWithSuccess:(void (^)(id object))success failure:(void (^)(NSError *error))failureรูปแบบแรกคือ -taskWithCompletion:(void (^)(id object, NSError *error))completionรูปแบบที่สองคือ

ความสำเร็จ: / ความล้มเหลว:

[target taskWithSuccess:^(id object) {
    // W00t! I've got my object
} failure:^(NSError *error) {
    // Oh noes! report the failure.
}];

เสร็จ:

[target taskWithCompletion:^(id object, NSError *error) {
    if (object) {
        // W00t! I've got my object
    } else {
        // Oh noes! report the failure.
    }
}];

รูปแบบที่ต้องการคืออะไร อะไรคือจุดแข็งและจุดอ่อน เมื่อไหร่ที่คุณจะใช้อันอื่น


ฉันค่อนข้างมั่นใจว่า Objective-C มีข้อยกเว้นในการจัดการการขว้างปา / จับมีเหตุผลที่คุณไม่สามารถใช้มันได้หรือไม่?
FrustratedWithFormsDesigner

อย่างใดอย่างหนึ่งเหล่านี้อนุญาตให้มีการผูกสาย async ซึ่งข้อยกเว้นไม่ได้ให้คุณ
Frank Shearar

5
@FrustratedWithFormsDesigner: stackoverflow.com/a/3678556/2289 - idiomatic objc ไม่ได้ใช้ try / catch สำหรับการควบคุมการไหล
Ant

1
โปรดลองย้ายคำตอบของคุณจากคำถามไปเป็นคำตอบ ... เพราะมันคือคำตอบ (และคุณสามารถตอบคำถามของคุณเอง)

1
ในที่สุดฉันก็รู้สึกกดดันเพื่อนและย้ายคำตอบของฉันไปเป็นคำตอบจริง
Jeffery Thomas

คำตอบ:


8

โทรกลับเสร็จสมบูรณ์ (ตรงข้ามกับคู่ที่ประสบความสำเร็จ / ล้มเหลว) เป็นเรื่องทั่วไปมากขึ้น หากคุณจำเป็นต้องเตรียมบริบทก่อนที่จะจัดการกับสถานะการส่งคืนคุณสามารถทำได้ก่อนประโยค "ถ้า (วัตถุ)" ในกรณีที่สำเร็จ / ล้มเหลวคุณต้องทำซ้ำรหัสนี้ ขึ้นอยู่กับความหมายของการติดต่อกลับ


ไม่สามารถแสดงความคิดเห็นกับคำถามเดิม ... ข้อยกเว้นไม่ใช่การควบคุมการไหลที่ถูกต้องในวัตถุประสงค์ -c (ดี, โกโก้) และไม่ควรใช้เช่นนี้ ข้อยกเว้นที่ส่งออกมาควรได้รับการจับเท่านั้นเพื่อยุติอย่างงดงาม

ใช่ฉันเห็นแล้วว่า หาก-task…สามารถส่งคืนวัตถุ แต่วัตถุนั้นไม่ได้อยู่ในสถานะที่ถูกต้องคุณจะยังคงต้องการการจัดการข้อผิดพลาดในสภาพความสำเร็จ
Jeffery Thomas

ใช่และถ้าบล็อกไม่ได้อยู่ในสถานที่ แต่ถูกส่งผ่านเป็นอาร์กิวเมนต์เพื่อควบคุมของคุณคุณต้องโยนสองบล็อกรอบ สิ่งนี้อาจน่าเบื่อเมื่อต้องโทรกลับผ่านหลายเลเยอร์ คุณสามารถแยก / เขียนมันกลับมาได้ตลอดเวลา

ฉันไม่เข้าใจว่าตัวจัดการการทำให้สมบูรณ์นั้นเป็นเรื่องทั่วไปมากขึ้นได้อย่างไร ความสมบูรณ์โดยทั่วไปจะเปลี่ยนหลายวิธีเป็นหนึ่งในรูปแบบของการบล็อกพารามิเตอร์ ทั่วไปยังหมายความว่าดีขึ้นหรือไม่ ใน MVC คุณมักจะมีรหัสที่ซ้ำกันในตัวควบคุมมุมมองเช่นกันนั่นเป็นความชั่วร้ายที่จำเป็นเนื่องจากการแยกข้อกังวล ฉันไม่คิดว่าเป็นเหตุผลที่อยู่ห่างจาก MVC
Boon

@Boon เหตุผลหนึ่งที่ฉันเห็น handler ตัวเดียวว่าเป็นสามัญมากกว่านั้นคือสำหรับกรณีที่คุณต้องการ callee / handler / block เองพิจารณาว่าการดำเนินการสำเร็จหรือล้มเหลว พิจารณากรณีความสำเร็จบางส่วนที่คุณอาจมีวัตถุที่มีข้อมูลบางส่วนและวัตถุข้อผิดพลาดของคุณคือข้อผิดพลาดที่ระบุว่าไม่ใช่ข้อมูลทั้งหมดที่ถูกส่งคืน บล็อกสามารถตรวจสอบข้อมูลเองและตรวจสอบว่าเพียงพอหรือไม่ สิ่งนี้เป็นไปไม่ได้กับสถานการณ์การโทรกลับสำเร็จ / ล้มเหลวไบนารี
เทรวิส

8

ฉันจะบอกว่าไม่ว่า API จะมีตัวจัดการความสมบูรณ์หนึ่งตัวหรือบล็อกความสำเร็จ / ความล้มเหลวคู่หนึ่งนั้นเป็นเรื่องของการตั้งค่าส่วนตัว

ทั้งสองวิธีมีข้อดีข้อเสียแม้ว่าจะมีความแตกต่างเพียงเล็กน้อยเท่านั้น

พิจารณาว่ายังมีตัวแปรเพิ่มเติมเช่นที่ตัวจัดการความสมบูรณ์หนึ่งตัวอาจมีเพียงหนึ่งพารามิเตอร์ที่รวมผลลัพธ์สุดท้ายหรือข้อผิดพลาดที่อาจเกิดขึ้น:

typedef void (^completion_t)(id result);

- (void) taskWithCompletion:(completion_t)completionHandler;

[self taskWithCompletion:^(id result){
    if ([result isKindOfError:[NSError class]) {
        NSLog(@"Error: %@", result);
    }
    else {
        ...
    }
}]; 

วัตถุประสงค์ของลายเซ็นนี้คือตัวจัดการความสมบูรณ์สามารถใช้ โดยทั่วไปใน API อื่น ๆ

ตัวอย่างเช่นในหมวดหมู่สำหรับ NSArray มีวิธีการforEachApplyTask:completion:ที่เรียกใช้งานตามลำดับสำหรับแต่ละวัตถุและแบ่งลูป IFF ที่มีข้อผิดพลาด เนื่องจากวิธีนี้เป็นแบบอะซิงโครนัสเช่นกันจึงมีตัวจัดการความสมบูรณ์เช่นกัน:

typedef void (^completion_t)(id result);
typedef void (^task_t)(id input, completion_t);
- (void) forEachApplyTask:(task_t)task completion:(completion_t);

ในความเป็นจริงcompletion_tตามที่นิยามไว้ข้างต้นนั้นเพียงพอและเพียงพอที่จะรองรับสถานการณ์ทั้งหมด

อย่างไรก็ตามมีวิธีการอื่นสำหรับงานอะซิงโครนัสเพื่อส่งสัญญาณการแจ้งเตือนการเสร็จสิ้นไปยังไซต์การโทร:

สัญญา

สัญญาที่เรียกว่า "อนาคต", "รอการตัดบัญชี" หรือ "ล่าช้า" เป็นตัวแทนผลลัพธ์สุดท้ายของงานแบบอะซิงโครนัส (ดูเพิ่มเติมที่: wiki Futures and contract )

ในขั้นต้นสัญญาอยู่ในสถานะ "รอ" นั่นคือ“ มูลค่า” ยังไม่ได้รับการประเมินและยังไม่พร้อมใช้งาน

ใน Objective-C สัญญาจะเป็นวัตถุธรรมดาซึ่งจะถูกส่งคืนจากวิธีอะซิงโครนัสตามที่แสดงด้านล่าง:

- (Promise*) doSomethingAsync;

! สถานะเริ่มต้นของสัญญาคือ“ รอดำเนินการ”

ในขณะเดียวกันภารกิจอะซิงโครนัสก็เริ่มประเมินผลลัพธ์

โปรดทราบว่าไม่มีตัวจัดการความสมบูรณ์ สัญญาจะให้วิธีที่มีประสิทธิภาพมากขึ้นซึ่งไซต์การโทรสามารถรับผลลัพธ์ในที่สุดของงานอะซิงโครนัสซึ่งเราจะเห็นในไม่ช้า

งานอะซิงโครนัสซึ่งสร้างวัตถุสัญญาต้องในที่สุด "แก้ไข" สัญญา ซึ่งหมายความว่าเนื่องจากงานอาจประสบความสำเร็จหรือล้มเหลวก็ต้อง“ ปฏิบัติตาม” สัญญาที่ส่งผ่านผลลัพธ์ที่ประเมินหรือต้อง“ ปฏิเสธ” สัญญาที่ส่งผ่านข้อผิดพลาดที่ระบุถึงสาเหตุของความล้มเหลว

! ในที่สุดงานก็ต้องแก้ไขสัญญา

เมื่อสัญญาได้รับการแก้ไขแล้วจะไม่สามารถเปลี่ยนแปลงสถานะได้อีกต่อไปรวมถึงมูลค่า

! สัญญาจะสามารถแก้ไขได้เพียงครั้งเดียว

เมื่อสัญญาได้รับการแก้ไขแล้วไซต์การโทรสามารถรับผลลัพธ์ได้ (ไม่ว่าจะล้มเหลวหรือประสบความสำเร็จ) การทำสิ่งนี้สำเร็จได้อย่างไรขึ้นอยู่กับว่าสัญญานั้นถูกนำไปใช้โดยใช้ซิงโครนัสหรือสไตล์อะซิงโครนัส

สัญญาสามารถนำไปใช้ในรูปแบบซิงโครนัสหรือแบบอะซิงโครนัสซึ่งนำไปสู่การปิดกั้นความหมายที่ไม่ปิดกั้นตามลำดับ

ในรูปแบบซิงโครนัสเพื่อดึงค่าของสัญญาไซต์การโทรจะใช้วิธีการที่จะบล็อกเธรดปัจจุบันจนกว่าหลังจากที่สัญญาได้รับการแก้ไขโดยงานแบบอะซิงโครนัสและผลลัพธ์สุดท้ายจะพร้อมใช้งาน

ในลักษณะอะซิงโครนัสไซต์การโทรจะลงทะเบียน callbacks หรือตัวจัดการบล็อกที่ถูกเรียกทันทีหลังจากสัญญาได้รับการแก้ไข

ปรากฎว่าสไตล์ซิงโครนัสมีข้อเสียที่สำคัญหลายประการซึ่งเอาชนะข้อดีของงานอะซิงโครนัสได้อย่างมีประสิทธิภาพ บทความที่น่าสนใจเกี่ยวกับการดำเนินงานในขณะนี้มีข้อบกพร่องของ“ฟิวเจอร์ส” ในมาตรฐาน C ++ 11 lib สามารถอ่านได้ที่นี่: Broken สัญญา-C ++ 0x ฟิวเจอร์ส

ใน Objective-C ไซต์ที่เรียกจะได้รับผลอย่างไร

มันเป็นการดีที่สุดที่จะแสดงตัวอย่าง มีห้องสมุดสองแห่งที่ใช้สัญญา (ดูลิงค์ด้านล่าง)

แต่สำหรับตัวอย่างโค้ดต่อไปผมจะใช้การดำเนินการโดยเฉพาะอย่างยิ่งของห้องสมุดสัญญาที่มีอยู่บน GitHub RXPromise ฉันเป็นผู้เขียน RXPromise

การใช้งานอื่น ๆ อาจมี API ที่คล้ายกัน แต่อาจมีความแตกต่างเล็กน้อยในด้านไวยากรณ์ RXPromise เป็นรุ่น Objective-C ของข้อกำหนดPromise / A +ซึ่งกำหนดมาตรฐานแบบเปิดสำหรับการใช้งานที่มีประสิทธิภาพและใช้งานร่วมกันได้ของสัญญาใน JavaScript

ไลบรารีสัญญาทั้งหมดที่แสดงด้านล่างใช้สไตล์อะซิงโครนัส

มีความแตกต่างอย่างมีนัยสำคัญระหว่างการใช้งานที่แตกต่างกัน RXPromise ใช้ lib การจัดส่งภายในซึ่งปลอดภัยต่อเธรดอย่างเต็มที่มีน้ำหนักเบามากและยังมีคุณสมบัติที่มีประโยชน์เพิ่มเติมมากมายเช่นการยกเลิก

ไซต์การเรียกรับผลลัพธ์ในที่สุดของงานอะซิงโครนัสผ่านตัวจัดการ“ การลงทะเบียน” “การสัญญา / A + สเปค” thenกำหนดวิธีการ

วิธีการ then

ด้วย RXPromise มันมีลักษณะดังนี้:

promise.then(successHandler, errorHandler);

ที่successHandlerเป็นบล็อกที่ถูกเรียกเมื่อสัญญาถูก“ เติมเต็ม” และ errorHandlerเป็นบล็อกที่ถูกเรียกเมื่อสัญญาถูก“ ปฏิเสธ”

! thenใช้เพื่อรับผลลัพธ์ในที่สุดและเพื่อกำหนดความสำเร็จหรือตัวจัดการข้อผิดพลาด

ใน RXPromise บล็อกตัวจัดการมีลายเซ็นต่อไปนี้:

typedef id (^success_handler_t)(id result);
typedef id (^error_handler_t)(NSError* error);

success_handler มีผลลัพธ์พารามิเตอร์ซึ่งเห็นได้ชัดว่าเป็นผลลัพธ์สุดท้ายของงานอะซิงโครนัส error_handler มีข้อผิดพลาดพารามิเตอร์ซึ่งเป็นข้อผิดพลาดที่รายงานโดยงานอะซิงโครนัสเมื่อมันล้มเหลว

บล็อกทั้งสองมีค่าส่งคืน ค่าตอบแทนนี้เกี่ยวกับอะไรจะกลายเป็นชัดเจนในไม่ช้า

ใน RXPromise thenเป็นคุณสมบัติที่ส่งคืนบล็อก บล็อกนี้มีสองพารามิเตอร์บล็อกตัวจัดการความสำเร็จและบล็อกตัวจัดการข้อผิดพลาด ตัวจัดการต้องถูกกำหนดโดยไซต์การโทร

! ตัวจัดการต้องถูกกำหนดโดยไซต์การโทร

ดังนั้นการแสดงออกpromise.then(success_handler, error_handler);เป็นรูปแบบสั้น ๆ ของ

then_block_t block promise.then;
block(success_handler, error_handler);

เราสามารถเขียนโค้ดที่กระชับยิ่งขึ้นได้:

doSomethingAsync
.then(^id(id result){
    
    return @“OK”;
}, nil);

รหัสอ่าน:“ Execute doSomethingAsync, เมื่อสำเร็จ, จากนั้นจึงใช้โปรแกรมจัดการความสำเร็จ”

ที่นี่ตัวจัดการข้อผิดพลาดnilหมายถึงในกรณีที่มีข้อผิดพลาดจะไม่ได้รับการจัดการในสัญญานี้

ข้อเท็จจริงสำคัญอีกประการหนึ่งคือการเรียกการบล็อกที่ส่งคืนจากคุณสมบัติthenจะส่งคืนสัญญา:

! then(...)ส่งคืนสัญญา

เมื่อโทรบล็อกกลับมาจากสถานที่ให้บริการthenที่“รับ” ผลตอบแทนใหม่สัญญาเป็นเด็กสัญญา ผู้รับจะกลายเป็นผู้ปกครองสัญญา

RXPromise* rootPromise = asyncA();
RXPromise* childPromise = rootPromise.then(successHandler, nil);
assert(childPromise.parent == rootPromise);

นั่นหมายความว่าอย่างไร?

ด้วยเหตุนี้เราจึงสามารถ“ เชื่อมโยง” งานอะซิงโครนัสซึ่งดำเนินการตามลำดับได้อย่างมีประสิทธิภาพ

นอกจากนี้ค่าส่งคืนของตัวจัดการทั้งสองจะกลายเป็น "มูลค่า" ของสัญญาที่ส่งคืน ดังนั้นหากงานประสบความสำเร็จกับผลลัพธ์ในที่สุด @“ ตกลง” สัญญาที่ส่งคืนจะ“ แก้ไข” (นั่นคือ“ เติมเต็ม”) ด้วยค่า @“ ตกลง”:

RXPromise* returnedPromise = asyncA().then(^id(id result){
    return @"OK";
}, nil);

...
assert([[returnedPromise get] isEqualToString:@"OK"]);

เช่นเดียวกันเมื่องานอะซิงโครนัสล้มเหลวสัญญาที่ส่งคืนจะได้รับการแก้ไข (นั่นคือ“ ถูกปฏิเสธ”) โดยมีข้อผิดพลาด

RXPromise* returnedPromise = asyncA().then(nil, ^id(NSError* error){
    return error;
});

...
assert([[returnedPromise get] isKindOfClass:[NSError class]]);

ผู้ดูแลอาจส่งคืนสัญญาอีกฉบับ ตัวอย่างเช่นเมื่อตัวจัดการนั้นดำเนินการภารกิจอะซิงโครนัสอื่น ด้วยกลไกนี้เราสามารถ“ เชื่อมโยง” งานอะซิงโครนัสได้:

RXPromise* returnedPromise = asyncA().then(^id(id result){
    return asyncB(result);
}, nil);

! ค่าส่งคืนของบล็อกตัวจัดการกลายเป็นค่าของสัญญาเด็ก

หากไม่มีสัญญาลูกค่าส่งคืนจะไม่มีผลใด ๆ

ตัวอย่างที่ซับซ้อนมากขึ้น:

ที่นี่เราจะดำเนินการasyncTaskA, asyncTaskB, asyncTaskCและasyncTaskD ตามลำดับ - และแต่ละงานที่ตามมาจะใช้เวลาของผลงานก่อนหน้านี้เป็น input ไปนี้:

asyncTaskA()
.then(^id(id result){
    return asyncTaskB(result);
}, nil)
.then(^id(id result){
    return asyncTaskC(result);
}, nil)
.then(^id(id result){
    return asyncTaskD(result);
}, nil)
.then(^id(id result){
    // handle result
    return nil;
}, nil);

"ห่วงโซ่" เช่นนี้เรียกว่า "ความต่อเนื่อง"

การจัดการข้อผิดพลาด

คำสัญญาทำให้ง่ายต่อการจัดการข้อผิดพลาด ข้อผิดพลาดจะถูก "ส่งต่อ" จากผู้ปกครองไปยังเด็กหากไม่มีการจัดการข้อผิดพลาดที่กำหนดไว้ในสัญญาผู้ปกครอง ข้อผิดพลาดจะถูกส่งต่อห่วงโซ่จนกว่าเด็กจะจัดการกับมัน ดังนั้นการมีข้อผิดพลาดข้างต้นเราสามารถใช้การจัดการข้อผิดพลาดเพียงแค่เพิ่ม“ ความต่อเนื่อง” อีกอันซึ่งเกี่ยวข้องกับข้อผิดพลาดที่อาจเกิดขึ้นได้ทุกที่ด้านบน :

asyncTaskA()
.then(^id(id result){
    return asyncTaskB(result);
}, nil)
.then(^id(id result){
    return asyncTaskC(result);
}, nil)
.then(^id(id result){
    return asyncTaskD(result);
}, nil)
.then(^id(id result){
    // handle result
    return nil;
}, nil);
.then(nil, ^id(NSError*error) {
    NSLog(@“”Error: %@“, error);
    return nil;
});

นี่คล้ายกับสไตล์ซิงโครนัสที่คุ้นเคยมากกว่าพร้อมการจัดการข้อยกเว้น:

try {
    id a = A();
    id b = B(a);
    id c = C(b);
    id d = D(c);
    // handle d
}
catch (NSError* error) {
    NSLog(@“”Error: %@“, error);
}

โดยทั่วไปสัญญามีคุณสมบัติที่มีประโยชน์อื่น ๆ :

ตัวอย่างเช่นมีการอ้างอิงถึงสัญญาผ่านทางthenหนึ่งสามารถ "ลงทะเบียน" เป็นตัวจัดการได้มากเท่าที่ต้องการ ใน RXPromise ตัวจัดการการลงทะเบียนสามารถเกิดขึ้นได้ตลอดเวลาและจากเธรดใด ๆ เนื่องจากเป็นเธรดที่ปลอดภัยอย่างสมบูรณ์

RXPromise มีคุณสมบัติการใช้งานที่มีประโยชน์มากกว่าสองประการโดยไม่จำเป็นต้องใช้ข้อกำหนดของสัญญา / A + หนึ่งคือ "ยกเลิก"

ปรากฎว่า "การยกเลิก" เป็นคุณสมบัติที่ทรงคุณค่าและมีความสำคัญ ตัวอย่างเช่นไซต์โทรที่มีการอ้างอิงถึงสัญญาสามารถส่งcancelข้อความเพื่อระบุว่าไม่สนใจผลลัพธ์ในที่สุด

แค่คิดว่างานแบบอะซิงโครนัสซึ่งโหลดรูปภาพจากเว็บและสิ่งที่จะแสดงในตัวควบคุมมุมมอง หากผู้ใช้ย้ายออกจากตัวควบคุมมุมมองปัจจุบันผู้พัฒนาอาจใช้โค้ดที่ส่งข้อความยกเลิกไปยังimagePromiseซึ่งจะทริกเกอร์ตัวจัดการข้อผิดพลาดที่กำหนดโดยการดำเนินการร้องขอ HTTP ซึ่งการร้องขอจะถูกยกเลิก

ใน RXPromise ข้อความยกเลิกจะถูกส่งต่อจากผู้ปกครองไปยังลูก ๆ ของมันเท่านั้น แต่จะไม่ส่งต่อในทางกลับกัน นั่นคือสัญญา“ รูท” จะยกเลิกสัญญาเด็กทั้งหมด แต่คำสัญญาของเด็กจะยกเลิกเฉพาะ "สาขา" ที่เป็นผู้ปกครอง ข้อความยกเลิกจะถูกส่งต่อไปยังเด็ก ๆ หากสัญญาได้รับการแก้ไขแล้ว

เป็นงานที่ไม่ตรงกันสามารถตัวเองลงทะเบียนจัดการสำหรับสัญญาของตัวเองและทำให้สามารถตรวจสอบได้เมื่อมีคนอื่นยกเลิก จากนั้นอาจหยุดทำงานก่อนกำหนดเป็นเวลานานและมีค่าใช้จ่ายสูง

นี่คือการใช้งานอื่น ๆ อีกสองอย่างของสัญญาใน Objective-C ที่พบใน GitHub:

https://github.com/Schoonology/aplus-objc
https://github.com/affablebloke/deferred-objective-c
https://github.com/bww/FutureKit
https://github.com/jkubicek/JKubpromises
https://github.com/Strilanc/ObjC-CollapsingFutures
https://github.com/b52/OMPromises
https://github.com/mproberts/objc-promise
https://github.com/klaaspieter/Promise
https: //github.com/jameswomack/Promise
https://github.com/nilfs/promise-objc
https://github.com/mxcl/PromiseKit
https://github.com/apleshkov/promises-aplus
https: // github.com/KptainO/Rebelle

และการดำเนินการของตัวเอง: RXPromise

รายการนี้มีแนวโน้มไม่สมบูรณ์!

เมื่อเลือกห้องสมุดที่สามสำหรับโครงการของคุณโปรดตรวจสอบอย่างรอบคอบว่าการใช้งานห้องสมุดเป็นไปตามข้อกำหนดเบื้องต้นที่ระบุไว้ด้านล่าง:

  • ห้องสมุดสัญญาที่เชื่อถือได้จะต้องปลอดภัย!

    มันคือทั้งหมดที่เกี่ยวกับการประมวลผลแบบอะซิงโครนัสและเราต้องการใช้ซีพียูหลายตัวและดำเนินการกับเธรดที่ต่างกันพร้อมกันทุกครั้งที่ทำได้ ระวังการใช้งานส่วนใหญ่จะไม่ปลอดภัย!

  • ตัวจัดการจะเรียกว่าแบบอะซิงโครนัสซึ่งเกี่ยวข้องกับไซต์การโทร! เสมอและไม่ว่าอะไรจะเกิดขึ้น!

    การใช้งานที่เหมาะสมควรทำตามรูปแบบที่เข้มงวดมากเมื่อเรียกใช้ฟังก์ชันอะซิงโครนัส ผู้ดำเนินการหลายคนมักจะ "เพิ่มประสิทธิภาพ" เคสซึ่งตัวจัดการจะถูกเรียกใช้พร้อมกันเมื่อสัญญาได้รับการแก้ไขแล้วเมื่อตัวจัดการจะลงทะเบียน สิ่งนี้สามารถทำให้เกิดปัญหาได้ทุกประเภท ดูอย่าปล่อย Zalgo! .

  • ควรมีกลไกในการยกเลิกสัญญา

    ความเป็นไปได้ที่จะยกเลิกงานแบบอะซิงโครนัสมักจะกลายเป็นข้อกำหนดที่มีลำดับความสำคัญสูงในการวิเคราะห์ความต้องการ ถ้าไม่เช่นนั้นจะมีการยื่นคำขอการปรับปรุงจากผู้ใช้ในภายหลังหลังจากที่ปล่อยแอพ เหตุผลควรชัดเจน: งานใด ๆ ที่อาจหยุดหรือใช้เวลานานเกินไปที่จะเสร็จสิ้นควรถูกยกเลิกโดยผู้ใช้หรือหมดเวลา ห้องสมุดสัญญาที่ดีควรสนับสนุนการยกเลิก


1
สิ่งนี้จะได้รับรางวัลสำหรับคำตอบที่ยาวที่สุดที่เคยมีมา แต่สำหรับความพยายาม :-)
คนเดินทาง

3

ฉันรู้ว่านี่เป็นคำถามเก่า แต่ฉันต้องตอบเพราะคำตอบของฉันแตกต่างจากคำถามอื่น

สำหรับคนที่บอกว่ามันเป็นเรื่องของความชอบส่วนตัวฉันต้องไม่เห็นด้วย มีเหตุผลที่ดีเหตุผลที่จะเลือกมากกว่า ...

ในกรณีที่เสร็จบล็อกของคุณจะถูกส่งสองวัตถุหนึ่งแสดงถึงความสำเร็จในขณะที่อื่น ๆ แสดงถึงความล้มเหลว ... ดังนั้นคุณจะทำอย่างไรถ้าทั้งสองเป็นศูนย์? คุณจะทำอย่างไรถ้าทั้งสองมีค่า? เหล่านี้เป็นคำถามที่สามารถหลีกเลี่ยงได้ในเวลารวบรวมและควรจะเป็น คุณหลีกเลี่ยงคำถามเหล่านี้โดยมีบล็อกสองบล็อกแยกกัน

การมีบล็อกความสำเร็จและความล้มเหลวแยกต่างหากทำให้โค้ดของคุณสามารถตรวจสอบได้


โปรดทราบว่าสิ่งต่าง ๆ เปลี่ยนแปลงด้วย Swift ในนั้นเราสามารถใช้แนวคิดของEitherenum เพื่อให้การรับประกันว่าบล็อกสมบูรณ์จะมีวัตถุหรือข้อผิดพลาดและต้องมีหนึ่งในนั้น ดังนั้นสำหรับ Swift บล็อกเดียวจึงดีกว่า


1

ฉันสงสัยว่ามันจะจบลงด้วยการตั้งค่าส่วนตัว ...

แต่ฉันชอบบล็อกสำเร็จ / ความล้มเหลวแยกต่างหาก ฉันชอบแยกตรรกะความสำเร็จ / ความล้มเหลว หากคุณประสบความสำเร็จหรือล้มเหลวหลายระดับคุณจะพบกับสิ่งที่อ่านได้ง่ายกว่า

ในฐานะที่เป็นตัวอย่างที่ค่อนข้างสุดขีดของการทำรังเช่นนี้ทับทิมบางตัวแสดงรูปแบบนี้


1
ฉันเคยเห็นโซ่ซ้อนกันของทั้งสอง ฉันคิดว่าพวกเขาทั้งคู่ดูแย่มาก แต่นั่นเป็นความเห็นส่วนตัวของฉัน
Jeffery Thomas

1
แต่คุณจะเชื่อมโยงการโทรแบบ async ได้อย่างไร
Frank Shearar

ฉันไม่รู้จักผู้ชาย…ฉันไม่รู้ ส่วนหนึ่งของเหตุผลที่ฉันถามคือเพราะฉันไม่ชอบว่าโค้ด async ของฉันมีลักษณะอย่างไร
Jeffery Thomas

แน่ใจ คุณเขียนโค้ดด้วยวิธีการส่งต่อซึ่งไม่น่าแปลกใจมากนัก (Haskell มีรูปแบบการทำด้วยเหตุผลนี้: ให้คุณเขียนในรูปแบบโดยตรงอย่างเห็นได้ชัด)
Frank Shearar

คุณอาจสนใจในการใช้งานสัญญา ObjC นี้: github.com/couchdeveloper/RXPromise
e1985

0

นี่ให้ความรู้สึกเหมือนเป็นตำรวจเต็มตัว แต่ฉันไม่คิดว่าจะมีคำตอบที่ถูกต้องอยู่ที่นี่ ฉันไปกับบล็อกเสร็จเพียงเพราะการจัดการข้อผิดพลาดอาจยังต้องทำในสภาพความสำเร็จเมื่อใช้บล็อกความสำเร็จ / ความล้มเหลว

ฉันคิดว่ารหัสสุดท้ายจะมีหน้าตาเป็นอย่างไร

[target taskWithCompletion:^(id object, NSError *error) {
    if (error) {
        // Oh noes! report the failure.
    } else if (![target validateObject:&object error:&error]) {
        // Oh noes! report the failure.
    } else {
        // W00t! I've got my object
    }
}];

หรือเพียงแค่

[target taskWithCompletion:^(id object, NSError *error) {
    if (error || ![target validateObject:&object error:&error]) {
        // Oh noes! report the failure.
        return;
    }

    // W00t! I've got my object
}];

ไม่ใช่โค้ดที่ดีที่สุดและการซ้อนจะแย่ลง

[target taskWithCompletion:^(id object, NSError *error) {
    if (error || ![target validateObject:&object error:&error]) {
        // Oh noes! report the failure.
        return;
    }

    [object objectTaskWithCompletion:^(id object2, NSError *error) {
        if (error || ![object validateObject2:&object2 error:&error]) {
            // Oh noes! report the failure.
            return;
        }

        // W00t! I've got object and object 2
    }];
}];

ฉันคิดว่าฉันจะไปไม่นาน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.