คุณจะคืนค่าหลายค่าจากสัญญาได้อย่างไร?


87

เมื่อเร็ว ๆ นี้ฉันต้องเจอกับสถานการณ์บางอย่างสองสามครั้งซึ่งฉันไม่รู้วิธีแก้ปัญหาอย่างถูกต้อง สมมติรหัสต่อไปนี้:

somethingAsync()
  .then( afterSomething )
  .then( afterSomethingElse )

function afterSomething( amazingData ) {
  return processAsync( amazingData );
}
function afterSomethingElse( processedData ) {
}

ตอนนี้สถานการณ์อาจเกิดขึ้นที่ฉันต้องการที่จะมีการเข้าถึงในamazingDataafterSomethingElse

วิธีแก้ปัญหาที่ชัดเจนวิธีหนึ่งคือการส่งคืนอาร์เรย์หรือแฮชจากafterSomethingเนื่องจากคุณสามารถส่งคืนค่าจากฟังก์ชันได้เพียงค่าเดียว แต่ฉันสงสัยว่ามีวิธีafterSomethingElseยอมรับ 2 พารามิเตอร์และเรียกใช้ในลักษณะเดียวกันหรือไม่เพราะดูเหมือนจะง่ายกว่ามากในการจัดทำเอกสารและทำความเข้าใจ

ฉันแค่สงสัยเกี่ยวกับความเป็นไปได้นี้เนื่องจากมีQ.spreadสิ่งที่คล้ายกับที่ฉันต้องการ




การยกเลิกการกำหนดโครงสร้างใน ES6 จะช่วยได้ ตรวจสอบที่นี่
Ravi Teja

คำตอบ:


88

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

คำสัญญาแก้ไขได้โดยเนื้อแท้ด้วยค่าเดียว - นี่เป็นส่วนหนึ่งของวิธีการทำงานของ Q, ข้อมูลจำเพาะของสัญญา / A + ทำงานอย่างไรและนามธรรมทำงานอย่างไร

สิ่งที่ใกล้เคียงที่สุดที่คุณจะได้รับคือการใช้Q.spreadและส่งคืนอาร์เรย์หรือใช้การทำลาย ES6 หากได้รับการสนับสนุนหรือคุณยินดีที่จะใช้เครื่องมือการถ่ายเทเช่น BabelJS

ในฐานะที่เป็นสำหรับการส่งผ่านบริบทลงห่วงโซ่สัญญาโปรดดูที่ยอมรับที่ดีเยี่ยม Bergi บนว่า


16
เกิดอะไรขึ้นกับการแก้ไขด้วยวัตถุที่มีคุณสมบัติหลายอย่าง ดูเหมือนเป็นวิธีง่ายๆในการดึงค่าหลายค่าออกจากการแก้ไข
jfriend00

6
ทำได้ดีมาก
Benjamin Gruenbaum

นอกจากนี้คุณยังสามารถขยาย Promise ให้มี.spread()เหมือน Bluebird แสดงในคำตอบที่เกี่ยวข้องนี้: stackoverflow.com/a/22776850/1624862
Kevin Ghadyani

พฤติกรรมของ Promise.all () ดูเหมือนจะขัดแย้งกับสิ่งนี้ Promise.all([a, b, c]).then(function(x, y, z) {...})ทำงานได้อย่างถูกต้องในเอ็นจิ้น Javascript สมัยใหม่ทั้งหมดที่มี x, y และ z ประเมินเป็นค่าที่แก้ไขแล้วของ a, b และ c ดังนั้นจึงถูกต้องมากขึ้นที่จะบอกว่าภาษาไม่ยอมให้คุณทำง่ายๆ (หรืออย่างมีสติ) จากรหัสผู้ใช้ (เพราะคุณสามารถส่งคืน Promise ได้โดยตรงจากประโยคนั้นคุณสามารถรวมค่าของคุณไว้ในสัญญาแล้วรวมคำเหล่านั้นด้วย Promise .all () เพื่อให้ได้พฤติกรรมที่ต้องการแม้ว่าจะอยู่ในลักษณะที่ซับซ้อน)
Austin Hemmelgarn

3
@AustinHemmelgarn ที่เป็นเท็จเพียงPromise.allบรรลุเป้าหมายกับอาร์เรย์ ในคือPromise.all([a,b]).then((a, b) => b undefinedนั่นเป็นเหตุผลที่คุณต้องทำ.then(([a, b]) =>ซึ่งเป็นการมอบหมายที่ทำลายโครงสร้าง
Benjamin Gruenbaum

39

คุณสามารถส่งผ่านได้เพียงค่าเดียว แต่สามารถเป็นอาร์เรย์ที่มีค่าทวีคูณภายในตัวอย่างเช่น

function step1(){
  let server = "myserver.com";
  let data = "so much data, very impresive";
  return Promise.resolve([server, data]);
}

ในอีกด้านหนึ่งคุณสามารถใช้นิพจน์การทำลายโครงสร้างสำหรับ ES2015 เพื่อรับค่าแต่ละค่า

function step2([server, data]){
  console.log(server); // print "myserver.com"
  console.log(data);   // print "so much data, very impresive"
  return Promise.resolve("done");
}

เพื่อเรียกทั้งสองสัญญาผูกมัดพวกเขา:

step1()
.then(step2)
.then((msg)=>{
  console.log(msg); // print "done"
})

5
เรียกว่าการทำลายล้างไม่ใช่ "deconstructor" และไม่ใช่ตัวดำเนินการ: - /
Bergi

3
Btw คุณสามารถใช้การทำลายโครงสร้างได้อย่างถูกต้องในพารามิเตอร์: function step2([server, data]) { …- ด้วยวิธีนี้คุณจะหลีกเลี่ยงที่จะกำหนดให้กับ globals โดยปริยาย และคุณควรใช้returnหรือPromise.resolveไม่ใช่ตัวnew Promiseสร้างในตัวอย่างของคุณ
Bergi

thans @Bergi สำหรับคำแนะนำ!
Alejandro Silva

20

คุณสามารถส่งคืนวัตถุที่มีทั้งสองค่าได้ - ไม่มีอะไรผิดปกติกับสิ่งนั้น

อีกกลยุทธ์หนึ่งคือการรักษามูลค่าผ่านการปิดแทนที่จะส่งผ่าน:

somethingAsync().then(afterSomething);

function afterSomething(amazingData) {
  return processAsync(amazingData).then(function (processedData) {
    // both amazingData and processedData are in scope here
  });
}

เต็มรูปแบบแทนที่จะเป็นแบบอินไลน์บางส่วน (เทียบเท่าเนื้อหาที่สอดคล้องกันมากขึ้น):

somethingAsync().then(function (amazingData) {
  return processAsync(amazingData).then(function (processedData) {
    // both amazingData and processedData are in scope here
  });
}

3
สามารถส่งคืนthenภายในได้thenหรือไม่? มันไม่ได้แอนตี้แบบ ?
robe007

เหมือนที่ @ robe007 พูดนี่จะไม่คล้ายกับ 'callback hell' หรือ? ที่นี่การทำรังของคุณแล้วบล็อกแทนที่จะเป็นฟังก์ชันเรียกกลับสิ่งนี้จะทำให้จุดประสงค์ของการมีสัญญา
Dheeraj

5

สองสิ่งที่คุณทำได้คือคืนวัตถุ

somethingAsync()
    .then( afterSomething )
    .then( afterSomethingElse );

function processAsync (amazingData) {
     //processSomething
     return {
         amazingData: amazingData, 
         processedData: processedData
     };
}

function afterSomething( amazingData ) {
    return processAsync( amazingData );
}

function afterSomethingElse( dataObj ) {
    let amazingData = dataObj.amazingData,
        processedData = dataObj.proccessedData;
}

ใช้ขอบเขต!

var amazingData;
somethingAsync()
  .then( afterSomething )
  .then( afterSomethingElse )

function afterSomething( returnedAmazingData ) {
  amazingData = returnedAmazingData;
  return processAsync( amazingData );
}
function afterSomethingElse( processedData ) {
  //use amazingData here
}

3

นี่คือวิธีที่ฉันคิดว่าคุณควรทำ

แยกโซ่

เนื่องจากทั้งสองฟังก์ชั่นจะใช้amazingDataจึงเหมาะสมที่จะมีไว้ในฟังก์ชันเฉพาะ ฉันมักจะทำแบบนั้นทุกครั้งที่ต้องการนำข้อมูลบางส่วนกลับมาใช้ใหม่ดังนั้นจึงแสดงเป็นฟังก์ชัน arg เสมอ

เนื่องจากตัวอย่างของคุณกำลังเรียกใช้โค้ดบางส่วนฉันจะสมมติว่ามีการประกาศทั้งหมดในฟังก์ชัน ผมจะเรียกมันว่าtoto () จากนั้นเราก็จะมีฟังก์ชั่นอื่นซึ่งจะใช้ทั้งafterSomething ()และafterSomethingElse ()

function toto() {
    return somethingAsync()
        .then( tata );
}

นอกจากนี้คุณจะสังเกตเห็นว่าฉันได้เพิ่มคำสั่งส่งคืนเนื่องจากโดยปกติแล้วเป็นวิธีที่จะไปกับคำสัญญา - คุณจะคืนคำสัญญาเสมอเพื่อให้เราสามารถผูกมัดต่อไปได้หากจำเป็น ที่นี่somethingAsync ()จะสร้างamazingDataและจะพร้อมใช้งานทุกที่ภายในฟังก์ชันใหม่

ตอนนี้ฟังก์ชั่นใหม่นี้จะมีลักษณะอย่างไรโดยทั่วไปขึ้นอยู่กับprocessAsync () asynchronous ด้วยหรือไม่?

processAsync ไม่ใช่อะซิงโครนัส

ไม่มีเหตุผลที่จะทำให้สิ่งต่าง ๆ ซับซ้อนมากเกินไปถ้าprocessAsync ()ไม่ใช่แบบอะซิงโครนัส รหัสลำดับที่ดีเก่า ๆ บางตัวจะทำให้ได้

function tata( amazingData ) {
    var processed = afterSomething( amazingData );
    return afterSomethingElse( amazingData, processed );
}

function afterSomething( amazingData ) {
    return processAsync( amazingData );
}
function afterSomethingElse( amazingData, processedData ) {
}

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

processAsync แบบอะซิงโครนัส

หากprocessAsync ()เป็นแบบอะซิงโครนัสโค้ดจะมีลักษณะแตกต่างกันเล็กน้อย ที่นี่เราจะพิจารณาว่าafterSomething ()และafterSomethingElse ()จะไม่ถูกนำกลับมาใช้ที่อื่น

function tata( amazingData ) {
    return afterSomething()
        .then( afterSomethingElse );

    function afterSomething( /* no args */ ) {
        return processAsync( amazingData );
    }
    function afterSomethingElse( processedData ) {
        /* amazingData can be accessed here */
    }
}

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


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

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

ตัวอย่างการรับประทานอาหาร

นี่คือตัวอย่าง:

function goingThroughTheEatingProcess(plenty, of, args, to, match, real, life) {
    return iAmAsync()
        .then(chew)
        .then(swallow);

        function chew(result) {
            return carefullyChewThis(plenty, of, args, "water", "piece of tooth", result);
        }

        function swallow(wine) {
            return nowIsTimeToSwallow(match, real, life, wine);
        }
}

function iAmAsync() {
    return Promise.resolve("mooooore");
}

function carefullyChewThis(plenty, of, args, and, some, more) {
    return true;
}

function nowIsTimeToSwallow(match, real, life, bobool) {
}

อย่าให้ความสำคัญกับPromise.resolve ()มากเกินไป เป็นเพียงวิธีที่รวดเร็วในการสร้างคำสัญญาที่ได้รับการแก้ไข สิ่งที่ฉันพยายามที่จะบรรลุตามนี้คือการมีทุกรหัสฉันทำงานในสถานที่เดียว - เพียงแค่ใต้thens ฟังก์ชันอื่น ๆ ทั้งหมดที่มีชื่อที่สื่อความหมายสามารถนำมาใช้ซ้ำได้

ข้อเสียเปรียบของเทคนิคนี้คือการกำหนดฟังก์ชันจำนวนมาก แต่มันเป็นความเจ็บปวดที่จำเป็นฉันกลัวเพื่อหลีกเลี่ยงการมีฟังก์ชั่นที่ไม่ระบุตัวตนทั่วทุกที่ และอะไรคือความเสี่ยงต่อไป: stack overflow? (เรื่องตลก!)


การใช้อาร์เรย์หรือวัตถุตามที่กำหนดไว้ในคำตอบอื่น ๆ ก็ใช้ได้เช่นกัน หนึ่งในวิธีที่นี้เป็นคำตอบที่เสนอโดยเควินเรด

นอกจากนี้คุณยังสามารถใช้ผูก ()หรือPromise.all () โปรดทราบว่าพวกเขายังต้องการให้คุณแยกรหัสของคุณ

ใช้ผูก

หากคุณต้องการที่จะให้การทำงานของคุณนำมาใช้ใหม่ แต่ไม่ได้จริงๆต้องให้สิ่งที่อยู่ภายในนั้นสั้นมากคุณสามารถใช้ผูก ()

function tata( amazingData ) {
    return afterSomething( amazingData )
        .then( afterSomethingElse.bind(null, amazingData) );
}

function afterSomething( amazingData ) {
    return processAsync( amazingData );
}
function afterSomethingElse( amazingData, processedData ) {
}

เพื่อให้ง่ายการผูก ()จะนำหน้ารายการ args (ยกเว้นอันแรก) ไปยังฟังก์ชันเมื่อมีการเรียกใช้

โดยใช้ Promise.all

ในโพสต์ของคุณคุณ mentionned การใช้งานของการแพร่กระจาย () ฉันไม่เคยใช้กรอบงานที่คุณใช้ แต่นี่คือวิธีที่คุณควรใช้

บางคนเชื่อว่าPromise.all ()เป็นวิธีแก้ปัญหาทั้งหมดดังนั้นฉันจึงสมควรกล่าวถึง

function tata( amazingData ) {
    return Promise.all( [ amazingData, afterSomething( amazingData ) ] )
        .then( afterSomethingElse );
}

function afterSomething( amazingData ) {
    return processAsync( amazingData );
}
function afterSomethingElse( args ) {
    var amazingData = args[0];
    var processedData = args[1];
}

คุณสามารถส่งผ่านข้อมูลไปยังPromise.all () - สังเกตการมีอยู่ของอาร์เรย์ - ตราบใดที่สัญญา แต่ตรวจสอบให้แน่ใจว่าไม่มีคำสัญญาใดล้มเหลวมิฉะนั้นจะหยุดประมวลผล

และแทนที่จะกำหนดตัวแปรใหม่จากอาร์กิวเมนต์ argsคุณควรจะสามารถใช้spread ()แทนthen ()สำหรับงานที่ยอดเยี่ยมทุกประเภท


3

เพียงแค่สร้างวัตถุและแยกอาร์กิวเมนต์จากวัตถุนั้น

let checkIfNumbersAddToTen = function (a, b) {
return new Promise(function (resolve, reject) {
 let c = parseInt(a)+parseInt(b);
 let promiseResolution = {
     c:c,
     d : c+c,
     x : 'RandomString'
 };
 if(c===10){
     resolve(promiseResolution);
 }else {
     reject('Not 10');
 }
});
};

ดึงอาร์กิวเมนต์จาก PromiseResolution

checkIfNumbersAddToTen(5,5).then(function (arguments) {
console.log('c:'+arguments.c);
console.log('d:'+arguments.d);
console.log('x:'+arguments.x);
},function (failure) {
console.log(failure);
});

2

ไม่ว่าคุณจะกลับมาจากคำสัญญาอะไรก็ตามจะถูกห่อเป็นสัญญาที่จะแกะออกใน.then()ขั้นตอนต่อไป

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

Promise.resolve([Promise.resolve(1), Promise.resolve(2), 3, 4])
       .then(([p1,p2,n1,n2]) => /* p1 and p2 are still promises */);

ในกรณีเหล่านี้จำเป็นอย่างยิ่งที่จะต้องใช้Promise.all()เพื่อรับp1และp2สัญญาที่ไม่ได้ห่อใน.then()ขั้นตอนต่อไปเช่น

Promise.resolve(Promise.all([Promise.resolve(1), Promise.resolve(2), 3, 4]))
       .then(([p1,p2,n1,n2]) => /* p1 is 1, p2 is 2, n1 is 3 and n2 is 4 */);

1

คุณสามารถตรวจสอบObservable ที่แสดงโดยRxjsช่วยให้คุณส่งคืนค่าได้มากกว่าหนึ่งค่า


0

เพียงส่งคืนทูเพิล:

async add(dto: TDto): Promise<TDto> {
console.log(`${this.storeName}.add(${dto})`);
return firebase.firestore().collection(this.dtoName)
  .withConverter<TDto>(this.converter)
  .add(dto)
  .then(d => [d.update(this.id, d.id), d.id] as [any, string])
  .then(x => this.get(x[1]));

}

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