Call An Asynchronous Javascript Function Synchronously

前端 未结 8 1390
南方客
南方客 2020-11-22 17:37

First, this is a very specific case of doing it the wrong way on-purpose to retrofit an asynchronous call into a very synchronous codebase that is many thousands of lines lo

相关标签:
8条回答
  • 2020-11-22 18:04

    Async functions, a feature in ES2017, make async code look sync by using promises (a particular form of async code) and the await keyword. Also notice in the code examples below the keyword async in front of the function keyword that signifies an async/await function. The await keyword won't work without being in a function pre-fixed with the async keyword. Since currently there is no exception to this that means no top level awaits will work (top level awaits meaning an await outside of any function). Though there is a proposal for top-level await.

    ES2017 was ratified (i.e. finalized) as the standard for JavaScript on June 27th, 2017. Async await may already work in your browser, but if not you can still use the functionality using a javascript transpiler like babel or traceur. Chrome 55 has full support of async functions. So if you have a newer browser you may be able to try out the code below.

    See kangax's es2017 compatibility table for browser compatibility.

    Here's an example async await function called doAsync which takes three one second pauses and prints the time difference after each pause from the start time:

    function timeoutPromise (time) {
      return new Promise(function (resolve) {
        setTimeout(function () {
          resolve(Date.now());
        }, time)
      })
    }
    
    function doSomethingAsync () {
      return timeoutPromise(1000);
    }
    
    async function doAsync () {
      var start = Date.now(), time;
      console.log(0);
      time = await doSomethingAsync();
      console.log(time - start);
      time = await doSomethingAsync();
      console.log(time - start);
      time = await doSomethingAsync();
      console.log(time - start);
    }
    
    doAsync();

    When the await keyword is placed before a promise value (in this case the promise value is the value returned by the function doSomethingAsync) the await keyword will pause execution of the function call, but it won't pause any other functions and it will continue executing other code until the promise resolves. After the promise resolves it will unwrap the value of the promise and you can think of the await and promise expression as now being replaced by that unwrapped value.

    So, since await just pauses waits for then unwraps a value before executing the rest of the line you can use it in for loops and inside function calls like in the below example which collects time differences awaited in an array and prints out the array.

    function timeoutPromise (time) {
      return new Promise(function (resolve) {
        setTimeout(function () {
          resolve(Date.now());
        }, time)
      })
    }
    
    function doSomethingAsync () {
      return timeoutPromise(1000);
    }
    
    // this calls each promise returning function one after the other
    async function doAsync () {
      var response = [];
      var start = Date.now();
      // each index is a promise returning function
      var promiseFuncs= [doSomethingAsync, doSomethingAsync, doSomethingAsync];
      for(var i = 0; i < promiseFuncs.length; ++i) {
        var promiseFunc = promiseFuncs[i];
        response.push(await promiseFunc() - start);
        console.log(response);
      }
      // do something with response which is an array of values that were from resolved promises.
      return response
    }
    
    doAsync().then(function (response) {
      console.log(response)
    })

    The async function itself returns a promise so you can use that as a promise with chaining like I do above or within another async await function.

    The function above would wait for each response before sending another request if you would like to send the requests concurrently you can use Promise.all.

    // no change
    function timeoutPromise (time) {
      return new Promise(function (resolve) {
        setTimeout(function () {
          resolve(Date.now());
        }, time)
      })
    }
    
    // no change
    function doSomethingAsync () {
      return timeoutPromise(1000);
    }
    
    // this function calls the async promise returning functions all at around the same time
    async function doAsync () {
      var start = Date.now();
      // we are now using promise all to await all promises to settle
      var responses = await Promise.all([doSomethingAsync(), doSomethingAsync(), doSomethingAsync()]);
      return responses.map(x=>x-start);
    }
    
    // no change
    doAsync().then(function (response) {
      console.log(response)
    })

    If the promise possibly rejects you can wrap it in a try catch or skip the try catch and let the error propagate to the async/await functions catch call. You should be careful not to leave promise errors unhandled especially in Node.js. Below are some examples that show off how errors work.

    function timeoutReject (time) {
      return new Promise(function (resolve, reject) {
        setTimeout(function () {
          reject(new Error("OOPS well you got an error at TIMESTAMP: " + Date.now()));
        }, time)
      })
    }
    
    function doErrorAsync () {
      return timeoutReject(1000);
    }
    
    var log = (...args)=>console.log(...args);
    var logErr = (...args)=>console.error(...args);
    
    async function unpropogatedError () {
      // promise is not awaited or returned so it does not propogate the error
      doErrorAsync();
      return "finished unpropogatedError successfully";
    }
    
    unpropogatedError().then(log).catch(logErr)
    
    async function handledError () {
      var start = Date.now();
      try {
        console.log((await doErrorAsync()) - start);
        console.log("past error");
      } catch (e) {
        console.log("in catch we handled the error");
      }
      
      return "finished handledError successfully";
    }
    
    handledError().then(log).catch(logErr)
    
    // example of how error propogates to chained catch method
    async function propogatedError () {
      var start = Date.now();
      var time = await doErrorAsync() - start;
      console.log(time - start);
      return "finished propogatedError successfully";
    }
    
    // this is what prints propogatedError's error.
    propogatedError().then(log).catch(logErr)

    If you go here you can see the finished proposals for upcoming ECMAScript versions.

    An alternative to this that can be used with just ES2015 (ES6) is to use a special function which wraps a generator function. Generator functions have a yield keyword which may be used to replicate the await keyword with a surrounding function. The yield keyword and generator function are a lot more general purpose and can do many more things then just what the async await function does. If you want a generator function wrapper that can be used to replicate async await I would check out co.js. By the way co's function much like async await functions return a promise. Honestly though at this point browser compatibility is about the same for both generator functions and async functions so if you just want the async await functionality you should use Async functions without co.js.

    Browser support is actually pretty good now for Async functions (as of 2017) in all major current browsers (Chrome, Safari, and Edge) except IE.

    0 讨论(0)
  • 2020-11-22 18:04

    What you want is actually possible now. If you can run the asynchronous code in a service worker, and the synchronous code in a web worker, then you can have the web worker send a synchronous XHR to the service worker, and while the service worker does the async things, the web worker's thread will wait. This is not a great approach, but it could work.

    0 讨论(0)
提交回复
热议问题