Translate

Monday 26 March 2018

The unhandled Promise rejection state and the others

Promises can be pleasant as well as vexing. In Javascript/node.js context, that is.

A promise can have three states: pending, fulfilled (resolved) or rejected.

Using a ES6 class, I managed to simplify the test that would demonstrate the three states and an additional, 'falsey' state!

First, the falsey state, as it can be confusing as an error message.


This message is caused when a Promise returns empty and is not handled in the test (mocha) either!

// mochatest.js

const assert=require('chai').assert
const asyncpromise=require('./asyncpromiserejection.js')
describe("asyncTests", () => {
it("handles Promise rejection",async ()=>{
    var t=new asyncpromise(false)
    await t.PTest().then(function () {
     console.log("Promise Resolved");
})
})        
});


// asyncpromiserejection.js

class asyncpromise{ 
    constructor(s){
        this.s=s
    }
PTest(){    
    var somevar = false;
    somevar=this.s;
    return new Promise(function (resolve, reject) {
        if (somevar === true)
            resolve();
        else
          // throw new Error("Promise rejcted")
            reject();
    });
}
}
module.exports=asyncpromise

As the else part in the code above does not do anything, it simply causes a promise rejection but since it is not handled in the test - there is no catch to handle the broken promise chain - it causes the message of a falsey.

Now, modify the test like so, by adding the catch block to handle the promise rejection state:

const assert=require('chai').assert
const asyncpromise=require('./asyncpromiserejection.js')
describe("asyncTests", () => {
it("handles Promise rejection",async ()=>{
    var t=new asyncpromise(false)
    await t.PTest().then(function () {
     console.log("Promise Resolved");
}).catch((error)=>{
    console.log("Promise rejected")
   // assert.equal(error.message,"Promise rejected")
})
})        
});




You can test for custom error messages returned by the rejected promise, like so:


class asyncpromise{ 
    constructor(s){
        this.s=s
    }
PTest(){    
    var somevar = false;
    somevar=this.s;
    return new Promise(function (resolve, reject) {
        if (somevar === true)
            resolve();
        else
           throw new Error("Promise rejcted")
           // reject();
    });
}
}
module.exports=asyncpromise


And the test, to include the assertion:

const assert=require('chai').assert
const asyncpromise=require('./asyncpromiserejection.js')
describe("asyncTests", () => {
it("handles Promise rejection",async ()=>{
    var t=new asyncpromise(false)
    await t.PTest().then(function () {
     console.log("Promise Resolved");
}).catch((error)=>{
   // console.log("Promise rejected")
    assert.equal(error.message,"Promise rejected")
})
})        
});



Make the test pass by simply correcting the typo in the error message!



The third state, of promise fulfilled, is simple.

If there are no undefined values, and all parameters passed to a method returning a promise are correct then the promise simply resolves, in this example, when 'true' is passed from the test, like so:


const assert=require('chai').assert
const asyncpromise=require('./asyncpromiserejection.js')
describe("asyncTests", () => {
it("handles Promise rejection",async ()=>{
    var t=new asyncpromise(true)
    await t.PTest().then(function () {
     console.log("Promise Resolved");
}).catch((error)=>{
   // console.log("Promise rejected")
    assert.equal(error.message,"Promise rejected")
})
})        

});





Nested promises 

When a chain of promises are executed and the sequence of execution is important, the key is to ensure that an async test awaits on the promises.

// mochatest

const command1=require('./init'),command2=require('./plan'), command3=require('./apply');
describe("asyncTests", () => {
it("handles Promise rejection",async ()=>{
    var filename, size, ipaddr
    await command1.init().then(function()
        {
            command2.plan(filename, size, ipaddr).then(function(){
               command3.apply(filename, size, ipaddr);
            }).catch(function (error,next){
                console.log("Error "+error)
                assert.equal("Error: plan",error)
                //next;
            }).then(function(){
               command3.apply(filename, size, ipaddr);
            })
            }).catch(function (error){
                console.log("Error "+error)               
            })
})     
});


// init.js

module.exports.init  = () => {
    return new Promise((resolve, reject,next) => {
        var v=false;
            if (v===true) {
                console.log("init"+v);
                throw new Error("init")
                //reject(v);
                next;
            } else {
                console.log("init"+v);
                resolve(v);
            }
    }
)
};

// plan.js

module.exports.plan = (filename, size, ipaddr) => {
    return new Promise((resolve, reject,next) => {
       var v=true;
            if (v===true) {
                console.log("plan"+v);
                throw new Error("plan")
               // reject(v);
               next;
            } else {
                console.log("plan"+v);
                resolve(v);
            }
\    }
)
};

// apply.js

module.exports.apply = (filename, size, ipaddr) => {
    return new Promise((resolve, reject) => {
       var v=false
            if (v===true) {
                console.log("apply"+v);
                throw new Error("apply")
                reject(v);
            } else {
                console.log("apply"+v);
                resolve(v);
            }
    }
)

};


The first test result in the screenshot above shows a normal mocha test method and the second, an async test.

The below screenshot shows when an exception occurs and is caught by the test which then calls on the promise that got broken from the chain due to the exception.


If the exception is not handled, however, it results in promise rejection as in the below screenshot:



Happy mocha-ing Node.js Promises!

No comments: