Michael
Michael

Reputation: 35

Resolve function in javascript promise doesn't behave consistently

The below function takes an array of words calls an api for info on each word and saves the data in the definitions object. I've used a promise to wait for a server response before returning any data.

function define(arr) { //pyramid of doom at the expense of adding abstraction
        return new Promise(function(resolve, reject) {
        var client = [];
        var definitions = {};
        for (var i = 0, len = arr.length; i < len; i++) {
            (function(i) {
                client[i] = new XMLHttpRequest();
                client[i].onreadystatechange = function() { 
                    if (client[i].readyState === 4 && client[i].status === 200) {
                    definitions[arr[i]] = JSON.parse(client[i].responseText);
                        if (Object.keys(definitions).length === arr.length) {
                        resolve(definitions); 
                        } 
                    } 
                };
                client[i].open('GET', 'http://api.wordnik.com:80/v4/word.json/' + arr[i] +
                '/definitions?limit=1&includeRelated=false&sourceDictionaries=all&useCanonical=false&includeTags=false&api_key=737061636520696e74656e74696f6e616c6c7920626c616e6',
                true);
                client[i].send();
                })(i);
            }
        });
    }

When every element with the arr argument is a word in the Wordnik API database the above program works fine. When a non word is passed in however the program breaks down. I would like to make it so any non word is either omitted or displays "definition not found."

The resolve function creates a click eventhandler and if a non word is passed to define then when the event handler is triggered by a click, the error "Uncaught TypeError: Cannot read property '0' of undefined" appears for a line with the code obj[this.id][0].text.

I tried adding in this conditional to the onreadystate anonymous function:

if (client[i].responseText[0] === undefined) {
    client.responseText[0] = {
        word: arr[i],
        text: 'Definition not found'
    };

but it doesn't fix anything.

Upvotes: 0

Views: 250

Answers (2)

Ry-
Ry-

Reputation: 225281

responseText is a string. You’re parsing it as JSON here:

definitions[arr[i]] = JSON.parse(client[i].responseText);

The problem of a definition not being found isn’t in your request code at all. Wherever you’re using the result of the promise is where you should be checking whether the definition array is empty. The place where the error occurs sounds promising:

… the error "Uncaught TypeError: Cannot read property '0' of undefined" appears for a line with the code obj[this.id][0].text.

Before reading obj[this.id][0].text, ensure that obj[this.id].length !== 0.

var definitions = obj[this.id];
var something =
    definitions.length === 0 ?
        'Definition not found' :
        definitions[0].text;

Also, you’re reimplementing Promise.all by counting keys. I’d suggest making a separate function to define one word.

var API_KEY = '737061636520696e74656e74696f6e616c6c7920626c616e6';

function queryString(map) {
    return '?' +
        Object.keys(map)
            .map(function (key) {
                return key + '=' + encodeURIComponent(map[key]);
            })
            .join('&');
}

function defineWord(word) {
    return new Promise(function (resolve, reject) {
        var request = new XMLHttpRequest();
        var uri =
            'http://api.wordnik.com/v4/word.json/' + encodeURIComponent(word) +
            '/definitions' + queryString({
                limit: 1,
                includeRelated: false,
                sourceDictionaries: 'all',
                useCanonical: false,
                includeTags: false,
                api_key: API_KEY,
            });

        request.addEventListener('error', reject);
        request.addEventListener('load', function () {
            resolve(this.response);
        });

        request.responseType = 'json';
        request.open('GET', uri, true);
        request.send(null);
    });
}

function define(words) {
    return Promise.all(words.map(defineWord))
        .then(function (results) {
            var definitions = {};

            results.forEach(function (result, i) {
                definitions[words[i]] = result;
            });

            return definitions;
        });
}

Upvotes: 1

SyncroIT
SyncroIT

Reputation: 1578

Your condition is correct, but the executed code is not. Just do something like this:

if (client[i].responseText[0] === undefined) {
    reject('Word not found'); 
}

Then in your code make the call to define(arr) looks something like this:

define(arr).catch(function(error) {
    alert(error);
});

This code is only an example, you can make it suitable for you!

Upvotes: 0

Related Questions