NodeJS UnhandledPromise การปฏิเสธคำเตือน


134

ดังนั้นฉันกำลังทดสอบส่วนประกอบที่ต้องอาศัยตัวปล่อยเหตุการณ์ เมื่อต้องการทำเช่นนั้นฉันจึงหาวิธีแก้ปัญหาโดยใช้สัญญากับมอคค่า + ชัย:

it('should transition with the correct event', (done) => {
  const cFSM = new CharacterFSM({}, emitter, transitions);
  let timeout = null;
  let resolved = false;
  new Promise((resolve, reject) => {
    emitter.once('action', resolve);
    emitter.emit('done', {});
    timeout = setTimeout(() => {
      if (!resolved) {
        reject('Timedout!');
      }
      clearTimeout(timeout);
    }, 100);
  }).then((state) => {
    resolved = true;
    assert(state.action === 'DONE', 'should change state');
    done();
  }).catch((error) => {
    assert.isNotOk(error,'Promise error');
    done();
  });
});

บนคอนโซลฉันได้รับ 'UnhandledPromiseRejectionWarning' ถึงแม้ว่าฟังก์ชั่นการปฏิเสธจะถูกเรียกใช้เพราะมันจะแสดงข้อความ 'AssertionError: Promise error' ทันที

(โหนด: 25754) UnhandledPromiseRejectionWarning: การปฏิเสธสัญญาที่ไม่สามารถจัดการได้ (รหัสปฏิเสธ: 2): AssertionError: ข้อผิดพลาดของสัญญา: คาดว่า {วัตถุ (ข้อความ showDiff, ... )} เป็นเท็จ 1) ควรเปลี่ยนด้วยเหตุการณ์ที่ถูกต้อง

หลังจากนั้น 2 วินาทีฉันก็จะได้

ข้อผิดพลาด: เกินหมดเวลาของ 2000ms ตรวจสอบให้แน่ใจว่าการโทรกลับเสร็จสิ้น () ถูกเรียกในการทดสอบนี้

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

ตอนนี้สิ่งที่ตลกถ้าฉันแสดงความคิดเห็นassert.isNotOk(error...)การทดสอบทำงานได้ดีโดยไม่มีการเตือนใด ๆ ในคอนโซล มันยังคง 'ล้มเหลว' ในแง่ที่ว่าจะทำการจับได้
แต่ถึงกระนั้นฉันไม่สามารถเข้าใจข้อผิดพลาดเหล่านี้ด้วยสัญญา ใครบางคนสามารถสอนฉันได้ไหม


ฉันคิดว่าคุณมีรั้งเสริมหนึ่งชุดและ parens ที่บรรทัดสุดท้าย โปรดลบพวกเขาและลองอีกครั้ง
Redu

4
นี่มันเจ๋งมากคำเตือนการปฏิเสธที่ไม่สามารถจัดการได้ใหม่จะค้นหาข้อบกพร่องในชีวิตจริงและช่วยประหยัดเวลา ชนะมากที่นี่ หากไม่มีการเตือนนี้การทดสอบของคุณจะหมดเวลาโดยไม่มีคำอธิบายใด ๆ
Benjamin Gruenbaum

คำตอบ:


161

ปัญหานี้เกิดจากสิ่งนี้:

.catch((error) => {
  assert.isNotOk(error,'Promise error');
  done();
});

หากการยืนยันล้มเหลวก็จะเกิดข้อผิดพลาด ข้อผิดพลาดนี้จะทำให้done()ไม่ถูกเรียกใช้เนื่องจากรหัสมีข้อผิดพลาดออกมาก่อน นั่นคือสิ่งที่ทำให้หมดเวลา

"สัญญาที่ไม่มีการจัดการปฏิเสธ"นอกจากนี้ยังมีสาเหตุมาจากการยืนยันล้มเหลวเพราะถ้ามีข้อผิดพลาดจะถูกโยนทิ้งในcatch()การจัดการ, และมีไม่ได้เป็นตามมาcatch()จัดการข้อผิดพลาดจะได้รับการกลืนกิน (ตามที่อธิบายไว้ในบทความนี้ ) UnhandledPromiseRejectionWarningเตือนจะแจ้งให้คุณทราบถึงความจริงนี้

โดยทั่วไปหากคุณต้องการทดสอบรหัสตามสัญญาใน Mocha คุณควรวางใจได้ว่า Mocha สามารถจัดการสัญญาได้แล้ว คุณไม่ควรใช้done()แต่ให้ส่งคืนสัญญาจากการทดสอบแทน มอคค่าจะตรวจพบข้อผิดพลาดใด ๆ เอง

แบบนี้:

it('should transition with the correct event', () => {
  ...
  return new Promise((resolve, reject) => {
    ...
  }).then((state) => {
    assert(state.action === 'DONE', 'should change state');
  })
  .catch((error) => {
    assert.isNotOk(error,'Promise error');
  });
});

7
สำหรับใครที่อยากรู้อยากเห็นนี่เป็นความจริงสำหรับดอกมะลิ
Nick Radford

@robertklep จะไม่ถูกเรียกว่าจะได้รับการเรียกสำหรับข้อผิดพลาดใด ๆ และไม่ใช่เพียงแค่ที่คุณคาดหวัง? ฉันคิดว่ารูปแบบนี้จะไม่ทำงานหากคุณพยายามยืนยันความล้มเหลว
TheCrazyProgrammer

1
@TheCrazyProgrammer จัดการควรอาจจะเป็นอาร์กิวเมนต์ที่สองที่จะcatch thenแต่ฉันไม่ได้อย่างสิ้นเชิงแน่ใจว่าสิ่งที่ตั้งใจของ OP ที่ถูกดังนั้นฉันทิ้งมันไว้ตามที่เป็น
robertklep

1
และสำหรับใครที่อยากรู้เรื่องดอกมะลิใช้done.fail('msg')ในกรณีนี้
Paweł

คุณสามารถให้ตัวอย่างเต็มของรหัสหลักเทียบกับการยืนยันควรไปไม่ชัดเจนที่นี่ ..... โดยเฉพาะถ้าคุณยืนยันค่าจากบริการเดิม / สัญญาโทรในรหัสของเรา สัญญาที่เกิดขึ้นจริงสำหรับรหัสของเราจะอยู่ใน "สัญญามอคค่า" อื่น ๆ นี้หรือไม่
bjm88

10

ฉันได้รับข้อผิดพลาดนี้เมื่อขัดด้วย sinon

การแก้ไขคือการใช้แพ็กเกจnpm แบบ sinon-as-contractเมื่อแก้ไขหรือปฏิเสธสัญญาด้วย stubs

แทน ...

sinon.stub(Database, 'connect').returns(Promise.reject( Error('oops') ))

ใช้ ...

require('sinon-as-promised');
sinon.stub(Database, 'connect').rejects(Error('oops'));

นอกจากนี้ยังมีวิธีการแก้ไข (หมายเหตุ s ที่สิ้นสุด)

ดูhttp://clarkdave.net/2016/09/node-v6-6-and-asynchronously-handled-promise-rejections


1
Sinon ขณะนี้รวมถึงวิธีการ "แก้ไข" และ "ปฏิเสธ" สำหรับสมบูรณ์เป็นรุ่น 2 ดูnpmjs.com/package/sinon-as-promised ฉันยัง +1 คำตอบอยู่แม้ว่า - ฉันไม่รู้เกี่ยวกับสิ่งนี้
Andrew

9

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

.catch((error) => {
  assert.isNotOk(error,'Promise error');
  done();
});

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

it('should transition with the correct event', (done) => {
  const cFSM = new CharacterFSM({}, emitter, transitions);
  let timeout = null;
  let resolved = false;
  new Promise((resolve, reject) => {
    emitter.once('action', resolve);
    emitter.emit('done', {});
    timeout = setTimeout(() => {
      if (!resolved) {
        reject('Timedout!');
      }
      clearTimeout(timeout);
    }, 100);
  }).then(((state) => {
    resolved = true;
    assert(state.action === 'DONE', 'should change state');
  })).then(done,done);
});

ฉันให้เครดิตกับบทความนี้สำหรับแนวคิดในการใช้. จากนั้นทำเสร็จเมื่อทดสอบสัญญาในมอคค่า


6

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

ตัวอย่างเช่นรหัสนี้จะแสดงคำเตือนที่รายงานในคำถามนี้:

new Promise((resolve, reject) => {
  return reject('Error reason!');
});

(node:XXXX) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: Error reason!

และการเพิ่ม.catch()หรือจัดการข้อผิดพลาดควรแก้ไขคำเตือน / ข้อผิดพลาด

new Promise((resolve, reject) => {
  return reject('Error reason!');
}).catch(() => { /* do whatever you want here */ });

หรือใช้พารามิเตอร์ตัวที่สองในthenฟังก์ชั่น

new Promise((resolve, reject) => {
  return reject('Error reason!');
}).then(null, () => { /* do whatever you want here */ });

1
แน่นอน แต่ผมคิดว่าในชีวิตจริงเรามักจะไม่ได้ใช้เพียงnew Promise((resolve, reject) => { return reject('Error reason!'); })แต่ในการทำงานfunction test() { return new Promise((resolve, reject) => { return reject('Error reason!'); });}เพื่อให้ฟังก์ชั่นภายในที่เราไม่จำเป็นต้องใช้.catch()แต่ประสบความสำเร็จในการจัดการกับข้อผิดพลาดมันก็เพียงพอที่จะใช้เมื่อเรียกว่าการทำงานtest().catch(e => console.log(e))หรือ async / รุ่นรอคอยtry { await test() } catch (e) { console.log(e) }
mikep

1

ฉันประสบปัญหานี้:

(โหนด: 1131004) UnhandledPromiseRejectionWarning: การปฏิเสธสัญญาที่ไม่สามารถจัดการได้ (re jection id: 1): TypeError: res.json ไม่ใช่ฟังก์ชั่น (โหนด: 1131004) การคัดค้านคำเตือน: การปฏิเสธสัญญาที่ไม่ได้จัดการจะถูกคัดค้าน ในอนาคตการปฏิเสธสัญญาที่ไม่ได้จัดการจะยุติกระบวนการของ Node.j ด้วยรหัสการออกที่ไม่เป็นศูนย์

มันเป็นความผิดพลาดของฉันฉันได้เปลี่ยนresวัตถุในthen(function(res)ดังนั้นจึงเปลี่ยนresเป็นผลลัพธ์และตอนนี้ก็ใช้งานได้

ไม่ถูกต้อง

module.exports.update = function(req, res){
        return Services.User.update(req.body)
                .then(function(res){//issue was here, res overwrite
                    return res.json(res);
                }, function(error){
                    return res.json({error:error.message});
                }).catch(function () {
                   console.log("Promise Rejected");
              });

การแก้ไข

module.exports.update = function(req, res){
        return Services.User.update(req.body)
                .then(function(result){//res replaced with result
                    return res.json(result);
                }, function(error){
                    return res.json({error:error.message});
                }).catch(function () {
                   console.log("Promise Rejected");
              });

รหัสบริการ:

function update(data){
   var id = new require('mongodb').ObjectID(data._id);
        userData = {
                    name:data.name,
                    email:data.email,
                    phone: data.phone
                };
 return collection.findAndModify(
          {_id:id}, // query
          [['_id','asc']],  // sort order
          {$set: userData}, // replacement
          { "new": true }
          ).then(function(doc) {
                if(!doc)
                    throw new Error('Record not updated.');
                return doc.value;   
          });
    }

module.exports = {
        update:update
}

1

นี่คือประสบการณ์ใช้ของฉันกับE7 async / คอย :

ในกรณีที่คุณได้รับasync helperFunction()เรียกจากการทดสอบของคุณ ... (หนึ่งคำอย่างชัดเจนด้วยasyncคำสำคัญES7 ฉันหมายถึง)

→ตรวจสอบให้แน่ใจว่าคุณเรียกมันว่าawait helperFunction(whateverParams) (ดีใช่ตามธรรมชาติเมื่อคุณรู้ว่า ... )

และเพื่อการทำงาน (เพื่อหลีกเลี่ยงการ 'รอเป็นคำสงวน') ฟังก์ชั่นทดสอบของคุณจะต้องมีเครื่องหมาย async ด้านนอก:

it('my test', async () => { ...

4
คุณไม่ได้await helperFunction(...)ที่จะเรียกว่าเป็น asyncฟังก์ชันส่งกลับสัญญา คุณสามารถจัดการสัญญาที่ส่งคืนเช่นเดียวกับที่คุณทำในฟังก์ชั่นที่ไม่ได้ระบุasyncว่าเกิดขึ้นเพื่อส่งคืนสัญญา จุดคือการจัดการสัญญาระยะเวลา ไม่ว่าจะเป็นฟังก์ชั่นasyncหรือไม่ก็ตาม awaitเป็นเพียงหนึ่งในหลาย ๆ วิธีในการจัดการกับสัญญา
หลุยส์

1
จริง แต่แล้วฉันก็ต้องลงทุนในการจับ ... หรือการทดสอบของฉันผ่านเป็นบวกเท็จและสัญญาที่ล้มเหลวใด ๆ ที่จะได้รับ unoticed (ในแง่ของผล testrunner) ดังนั้นการรอคอยดูเหมือนจะน้อยกว่าความพยายามของฉัน - อย่างไรก็ตามการรอคอยคือสิ่งที่ทำให้UnhandledPromiseRejectionWarningฉัน ... ดังนั้นคำตอบนี้
Frank Nocke

0

ฉันมีประสบการณ์คล้ายกันกับ Chai-Webdriver สำหรับ Selenium ฉันเพิ่มลงawaitในการยืนยันและแก้ไขปัญหา:

ตัวอย่างการใช้ Cucumberjs:

Then(/I see heading with the text of Tasks/, async function() {
    await chai.expect('h1').dom.to.contain.text('Tasks');
});

-7

ฉันแก้ไขปัญหานี้หลังจากถอนการติดตั้ง webpack (ตอบสนองปัญหา js)

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