Search code examples
javascriptaudioreturnblob

Return string from complicated function


Javascript question: How can I return my desired url from function shown here:

function getAudioBlob() {
 return recorder && recorder.exportWAV(function (blob) {
   var url = URL.createObjectURL(blob);
   console.log(url) // works great!
   return url;
 }, ("audio/mp3"));
}

console.log(getAudioBlob()); // the log reads 'undefined'

I'm having trouble getting the URL string (for an audio blob) returned from this function. My goal is to be able to use the Blob Url to upload an audio recording to the server.

My problem stems from the fact that I don't know how to catch a value returned from this awkward arrangement:

recorder && recorder.exportWAV(function (blob) {...

I placed a 'return' in front of it but that obviously doesn't work. How do I rewrite this such that it does? Thanks!


Solution

  • exportWAV is asynchronous so it finishes after your function executes. You can run some code when it's ready by using a callback or a Promise. For example:

    function getAudioBlob(recorder) {
      return new Promise((resolve) => {
        recorder.exportWAV((blob) => {
          const url = URL.createObjectURL(blob);
          resolve(url);
        }, 'audio/mp3');
      });
    }
    
    // Use the function..
    if (recorder) {
      getAudioBlob(recorder).then(url => console.log('blob url:', url));
    }
    

    When you're done with the url you should call URL.revokeObjectURL(url) to release the memory.

    I'm concerned by what you mean by "My goal is to be able to use the Blob Url to upload an audio recording to the server." The URL you created is for that client only, and only exists while that tab is open.

    And here's some PoC code for your issue as a whole:

    // This is a shorthand for writing a function that returns something.
    const getAudioBlob = recorder =>
      new Promise(resolve => recorder.exportWAV(resolve, 'audio/mp3'));
    
    function uploadBlobToServer(blob) {
      const fd = new FormData();
      fd.append('recording', blob, 'recording.mp3');
      return fetch('/api/recording/upload', { method: 'POST', body: fd });
    }
    
    function stopRecording() {
      if (!recorder) {
        console.error('No recorder found');
        return;
      }
    
      recorder.stop();
      getAudioBlob(recorder).then((blob) => {
        // Not sure what this is but guessing it needs to be called
        // AFTER exportWAV has finished?
        recorder.clear();
    
        // When we return a Promise inside another, the subsequent .then()/.catch()
        // Will have the result of this promise (in this case the result of the upload).
        return uploadBlobToServer(blob);
      })
        .then((res) => {
          if (res.status > 400) {
            throw new Error(res.statusText);
          }
        })
        .then((res) => {
          console.log('Recording uploaded to server successfully.', res.status, res.statusText);
        })
        .catch((error) => {
          console.error('Failed to upload recording to server.', error);
        });
    }
    
    // Or this is how you could write that function using asyc/await which is a bit cleaner
    // than using the Promises directly, but it's only supported by new browser versions.
    async function stopRecording() {
      if (!recorder) {
        console.error('No recorder found');
        return;
      }
    
      recorder.stop();
    
      try {
        const blob = await getAudioBlob(recorder);
        const res = await uploadBlobToServer(blob);
    
        // The HTTP Status code from the server was an error.
        if (res.status > 400) {
          throw new Error(res.statusText);
        }
    
        console.log('Recording uploaded to server successfully.', res.status, res.statusText);
    
        recorder.clear();
      } catch (error) {
        console.error('Failed to create and upload your recording:', error);
      }
    }