3

I am trying to see if a page/img successfully loads using javascript onload and onerror. I am attempting to read the status variable but am not able to do so when I assign the variable to read the status. I am attempting to use promises as outlined in the possible answers provides but still have some confusion.


const validateInput = (input) => {
  const errors = {};
  ... 

    if(!(isImgURlValid(input)))
    {
      errors = `wrong image'` 
    }
    ... 
  return errors;

const isImgURlValid = (path) => {
  let img = document.createElement('img');
  img.src = path;  
  let valid
 const promise = new Promise(resolve => {
    const img = new Image();
    img.onload = () => resolve({path, "status": 'ok'});
    img.onerror = () => resolve({path, "status": 'error'});
     img.src = path;
});
promise.then(function(val) { 
  console.log(val); 
  valid = val.status
}); 
console.log (valid)
}

//when I use async, my render functions no long render the errors properly
export const renderImgUrlInput = ({ input, label, type, size, required, meta: { touched, error } }) => (
  <div className={
    cs('form-group', size, {
      'has-error': touched && error,
      'required-input' : required
    })
  }>
    <label className="control-label" htmlFor={input.name}>{label}</label>
    <input {...input} placeholder={required ? 'Required' : ''} className="form-control" /> 
    {touched && error &&
      <span className="help-block">{error}</span>
    }
    {touched && !error &&
      <h1 className="help-block">{error} 'Image worked'</h1>
    }
  </div>
)

Lisa Dane
  • 33
  • 4
  • 1
    Does this answer your question? [How do I return the response from an asynchronous call?](https://stackoverflow.com/questions/14220321/how-do-i-return-the-response-from-an-asynchronous-call) – ponury-kostek Aug 18 '20 at 15:56
  • So that's the document I read and went ahead to use promises but still have some confusion about that. – Lisa Dane Aug 18 '20 at 16:02
  • You only have access to valid after the resolve call completed succesfully, @LisaDane, until then, valid will be undefined, as the promise hasn't been resolved or rejected yet. To be fair, your isImgURlValid should return the promise and not undefined as it is, a consumer of that function would then need to validate it using the then/catch chain – Icepickle Aug 18 '20 at 16:42
  • I see what you are saying. I edited my code and added a few more pieces to show it more in context. So how can i bubble up the value to my function isImgURLvalid – Lisa Dane Aug 18 '20 at 16:46
  • @LisaDane I updated my answer to reflect that, but that will not help you, you will have to handle the promises or result of async calls at a certain time in your code, so handle it early – Icepickle Aug 18 '20 at 16:56
  • @LisaDane This edit really changes the question a lot, but you are not showing us what we need, namely, from where do you render it, cause this is really what should send you the error state in the end. On the other hand, you could also revert, and create a new question, with risking that it gets the same duplicate topic again – Icepickle Aug 18 '20 at 19:01

3 Answers3

2

const isImgURLValid = (path) => {
    return new Promise((resolve, reject) => {
        const img = document.createElement("img");
        img.src = path;
        img.onload = resolve;
        img.onerror = reject;
        img.src = path;
        document.body.appendChild(img);
    });
};

isImgURLValid("https://www.gravatar.com/avatar/206601a888686677c4a74c89d9a2920f?s=48&d=identicon&r=PG")
    .then(() => console.log("Path is valid"))
    .catch(() => console.error("Path isn't valid"))
ponury-kostek
  • 7,824
  • 4
  • 23
  • 31
  • Thank you. That looks very clear. I edited my code where I was using the isImgURLValid as a check. I am confused as to how I can bubble up whether path is valild or not into my validate function. – Lisa Dane Aug 18 '20 at 16:48
  • @LisaDane You can't get value from promise in synchronous way. Please read this https://stackoverflow.com/questions/37220554/get-the-value-of-a-javascript-promise-in-a-synchronous-way – ponury-kostek Aug 18 '20 at 16:51
0

I guess you might be a bit new to promises, indicating you already read the suggested duplicate, but you can formulate your code anew in the following way

const isImgUrlValid = (path) => {
  return new Promise( (resolve, reject) => {
    const img = new Image();
    img.onload = () => resolve({path, "status": 'ok'});
    img.onerror = () => reject({path, "status": 'error'});
     img.src = path;
  });
};

// when it's found, resolve got called and we know it succeeded
isImgUrlValid('https://www.gravatar.com/avatar/153828e74e3fb5f7aeb19a28a78a378a?s=32&d=identicon&r=PG&f=1').then( status => console.log('image found') );

// when it's not found reject was called, and we need to catch it
isImgUrlValid('invalid_uri').then( _ => console.log('I will never be called') ).catch( err => console.log('no image found') );

This would then use resolve (success) and reject (failure) to use the normal flow of how promises work.

By returning the promise, any consumer can then use the then or catch chain to work with success or failures

Say you want to use the above code in a function, then it changes a bit, you can't really check this synchronously, unless the caller is an async function, in which case you could do it like:

const validateInput = async () => {
  let isValidImage = false;
  try {
   isValidImage = await isImgUrlValid( path );
  } catch (ex) {
    // not a correct image
    
  }
};

But this doesn't help you as now validateInput implicitly returns a promise, so then you have to handle this one as a promise.

Icepickle
  • 12,689
  • 3
  • 34
  • 48
  • I didn't quite understand your last sentence. What did you mean by this does not help me as of now. – Lisa Dane Aug 18 '20 at 17:04
  • @LisaDane it just means that at that point you will also have to handle the promise from the validateInput function, so if you handle the promise there or earlier or later in the sequence at a certain point you will have to handle it – Icepickle Aug 18 '20 at 17:09
0

You should return your created promise from isImgURlValid back to the caller. The caller can then wait for the promise to resolve and use the resolved value to see whether or not the provided image source was valid.

Here is an example inspired by the question code. You can enter an image source into the input box, then press Enter to trigger the event.

const isImgURlValid = (path) => { 
  return new Promise(resolve => {    
    const img = new Image()
    img.src = path;
    img.onload = () => resolve(true);
    img.onerror = () => resolve(false);
    
    // img starts loading src when added to the DOM
    document.body.append(img);
    document.body.removeChild(img);
  });
};

const validateInput = async (input) => {
  const errors = {};
  if (!await isImgURlValid(input)) {
    errors.url = "invalid";
  }
  return errors;
};

document.getElementById("input").addEventListener("change", ({target}) => {
  validateInput(target.value).then(console.log);
});
<input id="input" type="text" />
3limin4t0r
  • 19,353
  • 2
  • 31
  • 52
  • I like this a lot. Its quite clear especially since I am new to promises. – Lisa Dane Aug 18 '20 at 18:36
  • Actually this answer is good but unfortunately it throws off everything else when I make my validate function async. I am going to add the code to show you what I mean. – Lisa Dane Aug 18 '20 at 18:40
  • @LisaDane That's a bit what I pointed out in my answer, at a certain time, you have to handle the errors you were returning, so it has to be handled. You could mark this answer as answered, and start a new question for handling the error, not to make this question into a chameleon – Icepickle Aug 18 '20 at 19:04
  • @LisaDane It's not quite clear to me how the two initial functions and the render function are connected to one another. In React I would say that you do the following in the validation `then` callback; store the resulting validation data into the component state. This causes a re-render, in which you can then use the data or pass it further down to other components. – 3limin4t0r Aug 18 '20 at 20:30
  • `handleClick = (event) => asyncFn(event.target).then(data => setData(data))` then in your render result `{data &&
    do stuff with the async data
    }` or set `data` to a default value which would remove the need of `data &&`.
    – 3limin4t0r Aug 18 '20 at 20:34