เหตุใดฟังก์ชันอะซิงโครนัสของฉันจึงส่งคืน Promise {<pending>} แทนที่จะเป็นค่า


130

รหัสของฉัน:

let AuthUser = data => {
  return google.login(data.username, data.password).then(token => { return token } )
}

และเมื่อฉันพยายามเรียกใช้สิ่งนี้:

let userToken = AuthUser(data)
console.log(userToken)

ฉันได้รับ:

Promise { <pending> }

แต่ทำไม?

เป้าหมายหลักของฉันคือการได้รับโทเค็นgoogle.login(data.username, data.password)ซึ่งส่งคืนสัญญาเป็นตัวแปร จากนั้นให้ดำเนินการบางอย่างล่วงหน้า


1
@ LoïcFaure-Lacroix ดูบทความนี้: medium.com/@bluepnume/…
Src

@ LoïcFaure-Lacroix ดูgetFirstUserฟังก์ชัน
Src

แล้วมันล่ะ? เป็นฟังก์ชันที่คืนคำสัญญา
Loïc Faure-Lacroix

1
@ LoïcFaure-Lacroix ดังนั้นคุณหมายถึงแม้ในตัวอย่างนั้นเราจำเป็นต้องใช้เพื่อเข้าถึงสัญญาข้อมูลที่กลับมาในฟังก์ชัน getFirstUser?
Src

ในตัวอย่างนั้นใช่วิธีเดียวคือการใช้ไวยากรณ์ ES7 "รอ" ที่ดูเหมือนว่าจะแก้ไขหยุดการดำเนินการของบริบทปัจจุบันเพื่อรอผลของสัญญา หากคุณอ่านบทความคุณจะเห็น แต่เนื่องจาก ES7 อาจเกือบจะยังไม่มีที่ไหนรองรับใช่ "แล้ว" ก็น่ารักมาก
Loïc Faure-Lacroix

คำตอบ:


177

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

let AuthUser = function(data) {
  return google.login(data.username, data.password).then(token => { return token } )
}

let userToken = AuthUser(data)
console.log(userToken) // Promise { <pending> }

userToken.then(function(result) {
   console.log(result) // "Some User token"
})

ทำไมถึงเป็นเช่นนั้น?

คำสัญญาเป็นทิศทางไปข้างหน้าเท่านั้น คุณสามารถแก้ไขได้เพียงครั้งเดียว ค่าที่แก้ไขแล้วของ a Promiseจะถูกส่งไปยังเมธอด.thenหรือ.catch

รายละเอียด

ตามข้อกำหนดสัญญา / A +:

ขั้นตอนการแก้ปัญหาสัญญาคือการดำเนินการเชิงนามธรรมโดยใช้การป้อนคำสัญญาและค่าซึ่งเราระบุว่าเป็น [[แก้ไข]] (สัญญา, x) ถ้า x เป็นตัวแปรที่ทำได้ก็จะพยายามให้สัญญายอมรับสถานะของ x ภายใต้สมมติฐานที่ว่า x มีพฤติกรรมคล้ายคำสัญญาอย่างน้อยที่สุด มิฉะนั้นจะเป็นไปตามสัญญาด้วยค่า x

การปฏิบัติตามสัญญานี้ช่วยให้การดำเนินการตามคำสัญญาสามารถทำงานร่วมกันได้ตราบเท่าที่พวกเขาเปิดเผยวิธีการที่เป็นไปตามสัญญา / A + นอกจากนี้ยังช่วยให้การดำเนินการตามสัญญา / A + สามารถ "หลอมรวม" การใช้งานที่ไม่เป็นไปตามข้อกำหนดด้วยวิธีการที่สมเหตุสมผล

ข้อมูลจำเพาะนี้แยกวิเคราะห์ยากเล็กน้อยดังนั้นเรามาทำลายมันลง กฎคือ:

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

วิธีการใช้งานจริงได้อธิบายไว้ด้านล่างโดยละเอียด:

1. ผลตอบแทนของ.thenฟังก์ชันจะเป็นค่าที่แก้ไขแล้วของสัญญา

function initPromise() {
  return new Promise(function(res, rej) {
    res("initResolve");
  })
}

initPromise()
  .then(function(result) {
    console.log(result); // "initResolve"
    return "normalReturn";
  })
  .then(function(result) {
    console.log(result); // "normalReturn"
  });

2. ถ้า.thenฟังก์ชันส่งกลับแล้วค่าการแก้ไขสัญญาที่ถูกล่ามโซ่ที่ถูกส่งผ่านต่อไปนี้Promise.then

function initPromise() {
  return new Promise(function(res, rej) {
    res("initResolve");
  })
}

initPromise()
  .then(function(result) {
    console.log(result); // "initResolve"
    return new Promise(function(resolve, reject) {
       setTimeout(function() {
          resolve("secondPromise");
       }, 1000)
    })
  })
  .then(function(result) {
    console.log(result); // "secondPromise"
  });

อันแรกของคุณไม่ทำงาน Uncaught SyntaxError: Unexpected token .. คนที่สองต้องการการกลับมาสำหรับPromise
zamil

@zamil คุณต้องเรียกใช้ฟังก์ชันเช่นในตัวอย่างที่สอง คุณไม่สามารถ.thenใช้ฟังก์ชันที่ไม่ได้รับเชิญได้ อัปเดตคำตอบ
Bamieh

1
ฉันบุ๊กมาร์กไว้เพื่อให้เก็บไว้ได้ตลอด ฉันทำงานมานานมากเพื่อค้นหากฎที่ชัดเจนและอ่านได้อย่างแท้จริงเกี่ยวกับวิธีสร้างคำสัญญา บล็อกข้อความคำสัญญา / ข้อมูลจำเพาะ A + ของคุณเป็นตัวอย่างที่สมบูรณ์แบบว่าเหตุใดจึงเป็น PITA ในการสอนคำสัญญาด้วยตนเอง นอกจากนี้ยังเป็นครั้งเดียวที่ฉันเห็น setTimeout ใช้โดยที่มันไม่ทำให้บทเรียนสับสน และการอ้างอิงที่ดีเยี่ยมขอบคุณ
monsto

21

ฉันรู้ว่าคำถามนี้ถูกถามเมื่อ 2 ปีที่แล้ว แต่ฉันพบปัญหาเดียวกันและคำตอบสำหรับปัญหาคือตั้งแต่ ES6 ซึ่งคุณสามารถawaitส่งคืนค่าฟังก์ชันเช่น:

let AuthUser = function(data) {
  return google.login(data.username, data.password).then(token => { return token } )
}

let userToken = await AuthUser(data)
console.log(userToken) // your data

3
คุณไม่จำเป็นต้องใช้.then(token => return token)นั่นเป็นเพียงการส่งผ่านที่ไม่จำเป็น เพียงส่งคืนการโทรเข้าสู่ระบบของ Google
Soviut

คำตอบนี้ไม่เกี่ยวข้องกับคำถาม ปัญหาของโปสเตอร์ต้นฉบับไม่มีส่วนเกี่ยวข้องกับ ES6 'async / await คำสัญญามีขึ้นก่อนที่จะมีการใช้น้ำตาลวากยสัมพันธ์ใหม่ใน ECMAScript 2017 และพวกเขาใช้ Promises "under the hood" ดูMDN ใน async
ลองจับในที่สุด

สำหรับ ES8 / Nodejs ข้อผิดพลาดจะเกิดขึ้นหากคุณใช้awaitนอกฟังก์ชัน async บางทีตัวอย่างที่ดีกว่าในที่นี้ก็คือการสร้างAuthUserฟังก์ชันasyncซึ่งจะลงท้ายด้วยreturn await google.login(...);
Jon L.

4

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

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

let AuthUser = data => {   // just the login promise
  return google.login(data.username, data.password);
};

AuthUser(data).then( processLogin).catch(loginFail);

function processLogin( token) {
      // do logged in stuff:
      // enable, initiate, or do things after login
}
function loginFail( err) {
      console.log("login failed: " + err);
}

1

ดูส่วน MDN เกี่ยวกับสัญญา โดยเฉพาะอย่างยิ่งดูที่ประเภทผลตอบแทนของthen ()

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

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

let AuthUser = data => {
  return google
    .login(data.username, data.password)
    .then( token => {
      return token;
    });
};

คำสั่งreturn token;หมายความว่าฟังก์ชันที่ไม่ระบุตัวตนที่ถูกส่งผ่านไปthen()ควรส่งคืนโทเค็นไม่ใช่ว่าAuthUserฟังก์ชันควร สิ่งที่AuthUserส่งกลับมาคือผลของการโทรgoogle.login(username, password).then(callback);ซึ่งเป็นคำสัญญา

ในที่สุดการโทรกลับของคุณtoken => { return token; }ไม่ได้ทำอะไรเลย สิ่งที่คุณป้อนthen()ต้องเป็นฟังก์ชันที่จัดการโทเค็นไม่ทางใดก็ทางหนึ่ง


@Src ฉันเขียนคำตอบของฉันก่อนที่ผู้ถามจะชี้แจงว่าพวกเขากำลังมองหาวิธีส่งคืนค่าแบบซิงโครนัสและไม่มีการตั้งสมมติฐานเกี่ยวกับสภาพแวดล้อมการพัฒนาหรือเวอร์ชันภาษาเกินกว่าที่ข้อมูลโค้ดจะอนุมานได้นั่นคือมันปลอดภัย ถือว่า ES6 แต่ไม่จำเป็นต้องเป็น ES7
Jesse Amano

@AhmadBamieh เอาล่ะจะทำ ฉันสมมติว่าปัญหาคือฉันเข้าใจผิดว่าreturnได้รับการปฏิบัติอย่างไรกับไวยากรณ์การปิด (ish) ใหม่ซึ่งในกรณีนี้ - ฉันไม่เห็นด้วยอย่างยิ่งในเรื่องนี้ แต่ข้อผิดพลาดยังคงเป็นของฉันและฉันต้องขออภัยด้วย
Jesse Amano

2
@AhmadBamieh Er ฉันรู้จริงในส่วนนั้นซึ่งเป็นเหตุผลว่าทำไมฉันถึงยืนยันว่าtoken => { return token; } ไม่ทำอะไรเลยตรงข้ามกับการอ้างว่ามันต่อต้าน คุณสามารถพูดได้google.login(username, password).then(token=>{return token;}).then(token=>{return token;})และอื่น ๆ ตลอดไป แต่คุณจะประสบความสำเร็จกลับPromiseที่แก้ไขด้วย google.login(username, password);token-เช่นเดียวกับถ้าคุณเพียงแค่ทิ้งมันไว้เป็น ฉันไม่แน่ใจว่าทำไมคุณถึงรู้สึกว่า "ผิดมาก"
Jesse Amano

1
@AhmadBamieh: คุณสามารถระบุสิ่งที่ผิดพลาดในข้อความนี้ได้หรือไม่? ฉันไม่เห็นอะไรเลยเขาแค่อธิบายว่าทำไมreturn tokenไม่ทำงานตามที่ OP คาดไว้
Bergi

3
@AhmadBamieh: มีความเข้าใจผิดแน่นอน เราทั้งสามคนรู้ดีว่าคำสัญญาทำงานอย่างไรคำสั่งนั้นpromise.then(result => { return result; })เทียบเท่ากันหมดpromiseดังนั้นการเรียกเมธอดจึงไม่ทำอะไรเลยและควรทิ้งเพื่อลดความซับซ้อนของโค้ดและเพิ่มความสามารถในการอ่านซึ่งเป็นคำสั่งที่เป็นจริงอย่างสมบูรณ์
Bergi

1

สัญญาของคุณอยู่ระหว่างรอดำเนินการให้เสร็จสิ้นโดย

userToken.then(function(result){
console.log(result)
})

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

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