神刀安全网

ES6 Patterns: Converting Callbacks to Promises

I’ve been writing code using the new features defined in the ECMAScript 2015 version of JavaScript ( more commonly known as ES6 ) since January. Throughout the year I’ve seen myself start using a few new patterns that I think make my code better. I’m going to share them here with a few quick hitter articles. If you missed it, I wrote about clean higher order functions earlier this week. This post focuses on working with asynchronous code in ES6.

Background

One of the nicest new features of ES6 JavaScript is the standardization of Promises. Promises are a method for managing asynchronous code that serve as an alternative to the standard callback function syntax that has been the JavaScript standard for years. If you’re unfamiliar with them, a good example of a Promise based API is the new fetch api provided by browsers. fetch is a replacement for the older callback based XMLHttpRequest API. A quick example of an HTTP request with the 2 APIs provides a nice comparison of how Promises can lead to clearer code.

A simple get request with XMLHttpRequest looks like this:

function reqListener() {     var data = JSON.parse(this.responseText);     console.log(data);   }  function reqError(err) {     console.log('Fetch Error :-S', err);   }  var request = new XMLHttpRequest();   request.onload = function() {     var data = JSON.parse(this.responseText);     //do stuff with data };    request.onerror = function() {       alert('There was a problem with the request'); } request.open('get', '/api/foo/bar', true);   request.send();   

whereas with fetch we get this instead

fetch('/api/foo/bar').then(function(data) {       return data.json(); }).then(function(jsonData) {     //do stuff with the data }).catch(function(e) {     alert('There was a problem with the request'); }) 

This is admittedly a slightly unfair example due to XMLHttpRequest’s clunky object oriented API, but the key take away here is that promises allow for easier visualization of a program’s flow, as well as the ability to easily chain both synchronous and asynchronous operations together into a unified API.

Promises have been around for a little while in user-land. There are a bunch of Promise libraries out there that eventually standardized on a spec called Promises/A+ . Promises/A+ compliant libraries include Q , Bluebird , and rsvp . There are also many older libraries that provide Promise-like capabilities but are not completely spec compatible, most notably jQuery deferreds . But with ES6 Promises are being standardized. Happily, since the implementation uses the standard that user-land libaries have agreed upon, the Promise spec is compatible with existing implementations, and existing code bases can remove their existing libraries in favor of the browser supplied version, or have code written to use the browser version interop cleanly with their existing code. Support for Promises now exists in the latest versions of all major browsers, but it never made it to Internet Explorer and is Edge only for Microsoft browsers. So most developers will still want to consider using a polyfill for the time being.

Converting callback-driven code to use Promises

If you believe that Promises are worthwhile, you’ll immediately encounter a problem in today’s JavaScript world. Many JavaScript APIs, including most standard browser APIs and older but still popular libraries like jQuery and Backbone are heavily callback driven. Rather than mixing 2 different styles of asynchronous code, wouldn’t it be nice if we could easily convert callback-based APIs to use Promises? It turns out that it’s not that hard. Let’s take the simplest example possible to start. setTimeout is a straightforward browser API that waits for a specified period of time and then executes a callback. A standard use looks like this:

function doStuff() {/*...*/}  setTimeout(doStuff, 300);   

A Promise-based API for this function would likely look something like this code.

timeout(300).then(doStuff)   

We can create an API like that using setTimeout. To do that, we’ll need a function timeout which takes a timeout variable and returns a Promise.

You can define A+ compliant Promises using the Promise constructor, which expects a single function as an argument. That function takes 2 arguments, a resolve function and a reject function. The wonderful thing is that under the covers these are just callback functions that the Promise api glosses over.

Since we already have an API that can handle callbacks, the implementation of our timeout function is pretty simple.

function timeout(delay) {       return new Promise(function(resolve, reject) {         setTimeout(resolve, delay);      }); } 

We don’t use the reject callback, since setTimeout doesn’t provide any hooks for an error state. So we pass resolve as the callback to setTimeout, and that is all we need. Now we have a great chainable setTimeout function that we could include in a Promise chain.

Moving on to a more complicated example, let’s take our XMLHttpRequest code from above and see if we can create a simplified version of the fetch API using XMLHttpRequest under the covers. In this case I’m going to use ES6 style arrow functions to reduce the boilerplate a bit.

const fetch = (url, options = {method:'get'}) => new Promise((resolve, reject) => {       let request = new XMLHttpRequest();       request.onload = resolve      request.onerror = reject;     request.open(options.method, url, true);       request.send(); }); 

This is a simplified implementation that doesn’t come close to covering all the use cases of fetch, but it provides a great example of how simple it can be to transform a callback based api to a Promise based one, without having to rewrite the existing code 1 .

For what it’s worth, it is equally easy to convert functions in the other direction. For instance a callback based implementation of fetch can be a one liner.

const callbackFetch = (url, options, succ, err) => fetch(url, options).then(succ).catch(err);   

It’s useful to know that the various syntaxes for asynchronous code in JavaScript are effectively equivalent and interoperable. When designing APIs for your code, both for public libraries with external libraries and components that you use in an application, these patterns can be useful for providing a consistent API, even if you’re using code under the covers that presents it’s asynchronous code in a different way than your API.

原文  http://benmccormick.org/2015/12/30/es6-patterns-converting-callbacks-to-promises/

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » ES6 Patterns: Converting Callbacks to Promises

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
分享按钮