Javascript error parse error

The JavaScript exceptions thrown by JSON.parse() occur when string failed to be parsed as JSON.

The JavaScript exceptions thrown by JSON.parse() occur when string failed
to be parsed as JSON.

Message

SyntaxError: JSON.parse: unterminated string literal
SyntaxError: JSON.parse: bad control character in string literal
SyntaxError: JSON.parse: bad character in string literal
SyntaxError: JSON.parse: bad Unicode escape
SyntaxError: JSON.parse: bad escape character
SyntaxError: JSON.parse: unterminated string
SyntaxError: JSON.parse: no number after minus sign
SyntaxError: JSON.parse: unexpected non-digit
SyntaxError: JSON.parse: missing digits after decimal point
SyntaxError: JSON.parse: unterminated fractional number
SyntaxError: JSON.parse: missing digits after exponent indicator
SyntaxError: JSON.parse: missing digits after exponent sign
SyntaxError: JSON.parse: exponent part is missing a number
SyntaxError: JSON.parse: unexpected end of data
SyntaxError: JSON.parse: unexpected keyword
SyntaxError: JSON.parse: unexpected character
SyntaxError: JSON.parse: end of data while reading object contents
SyntaxError: JSON.parse: expected property name or '}'
SyntaxError: JSON.parse: end of data when ',' or ']' was expected
SyntaxError: JSON.parse: expected ',' or ']' after array element
SyntaxError: JSON.parse: end of data when property name was expected
SyntaxError: JSON.parse: expected double-quoted property name
SyntaxError: JSON.parse: end of data after property name when ':' was expected
SyntaxError: JSON.parse: expected ':' after property name in object
SyntaxError: JSON.parse: end of data after property value in object
SyntaxError: JSON.parse: expected ',' or '}' after property value in object
SyntaxError: JSON.parse: expected ',' or '}' after property-value pair in object literal
SyntaxError: JSON.parse: property names must be double-quoted strings
SyntaxError: JSON.parse: expected property name or '}'
SyntaxError: JSON.parse: unexpected character
SyntaxError: JSON.parse: unexpected non-whitespace character after JSON data

Error type

What went wrong?

JSON.parse() parses a string as JSON. This string has to be valid JSON
and will throw this error if incorrect syntax was encountered.

Examples

JSON.parse() does not allow trailing commas

Both lines will throw a SyntaxError:

JSON.parse("[1, 2, 3, 4,]");
JSON.parse('{"foo": 1,}');
// SyntaxError JSON.parse: unexpected character
// at line 1 column 14 of the JSON data

Omit the trailing commas to parse the JSON correctly:

JSON.parse("[1, 2, 3, 4]");
JSON.parse('{"foo": 1}');

Property names must be double-quoted strings

You cannot use single-quotes around properties, like ‘foo’.

JSON.parse("{'foo': 1}");
// SyntaxError: JSON.parse: expected property name or '}'
// at line 1 column 2 of the JSON data

Instead write «foo»:

JSON.parse('{"foo": 1}');

Leading zeros and decimal points

You cannot use leading zeros, like 01, and decimal points must be followed by at least
one digit.

JSON.parse('{"foo": 01}');
// SyntaxError: JSON.parse: expected ',' or '}' after property value
// in object at line 1 column 2 of the JSON data

JSON.parse('{"foo": 1.}');
// SyntaxError: JSON.parse: unterminated fractional number
// at line 1 column 2 of the JSON data

Instead write just 1 without a zero and use at least one digit after a decimal point:

JSON.parse('{"foo": 1}');
JSON.parse('{"foo": 1.0}');

See also

    Table of contents

  • Finding Parse Errors in Javascript
  • JavaScript Error Handling – SyntaxError: JSON.parse: bad parsing
  • JavaScript SyntaxError – JSON.parse: bad parsing
  • ValidateJavaScript — Find & Fix JavaScript Errors
  • SyntaxError: JSON.parse: bad parsing
  • JavaScript — Errors & Exceptions Handling
  • Getting Started

Finding Parse Errors in Javascript

var foo = {
  prop1 : 'value',
  prop2 : 'value',
  prop2 : 'value',   // <-- the problem
};

JavaScript Error Handling – SyntaxError: JSON.parse: bad parsing

{
    "first": "Jane",
    "last": "Doe"
}
[
    "Jane",
    "Doe"
]
var printError = function(error, explicit) {
    console.log(`[${explicit ? 'EXPLICIT' : 'INEXPLICIT'}] ${error.name}: ${error.message}`);
}

try {
    var json = `
        {
            "first": "Jane",
            "last": "Doe",
        }
    `
    console.log(JSON.parse(json));
} catch (e) {
    if (e instanceof SyntaxError) {
        printError(e, true);
    } else {
        printError(e, false);
    }
}
[EXPLICIT] SyntaxError: Unexpected token } in JSON at position 107
var printError = function(error, explicit) {
    console.log(`[${explicit ? 'EXPLICIT' : 'INEXPLICIT'}] ${error.name}: ${error.message}`);
}

try {
    var json = `
        {
            "first": "Jane",
            last: "Doe",
        }
    `
    console.log(JSON.parse(json));
} catch (e) {
    if (e instanceof SyntaxError) {
        printError(e, true);
    } else {
        printError(e, false);
    }
}
[EXPLICIT] SyntaxError: Unexpected token l in JSON at position 76

JavaScript SyntaxError – JSON.parse: bad parsing

SyntaxError: JSON.parse: unterminated string literal
SyntaxError: JSON.parse: bad control character in string literal
SyntaxError: JSON.parse: bad character in string literal
SyntaxError: JSON.parse: bad Unicode escape
SyntaxError: JSON.parse: bad escape character
SyntaxError: JSON.parse: unterminated string
SyntaxError: JSON.parse: no number after minus sign
SyntaxError: JSON.parse: unexpected non-digit
SyntaxError: JSON.parse: missing digits after decimal point
SyntaxError: JSON.parse: unterminated fractional number
SyntaxError: JSON.parse: missing digits after exponent indicator
SyntaxError: JSON.parse: missing digits after exponent sign
SyntaxError: JSON.parse: exponent part is missing a number
SyntaxError: JSON.parse: unexpected end of data
SyntaxError: JSON.parse: unexpected keyword
SyntaxError: JSON.parse: unexpected character
SyntaxError: JSON.parse: end of data while reading object contents
SyntaxError: JSON.parse: expected property name or '}'
SyntaxError: JSON.parse: end of data when ',' or ']' was expected
SyntaxError: JSON.parse: expected ',' or ']' after array element
SyntaxError: JSON.parse: end of data when property name was expected
SyntaxError: JSON.parse: expected double-quoted property name
SyntaxError: JSON.parse: end of data after property name when ':' 
             was expected
SyntaxError: JSON.parse: expected ':' after property name in object
SyntaxError: JSON.parse: end of data after property value in object
SyntaxError: JSON.parse: expected ',' or '}' after property value in 
             object
SyntaxError: JSON.parse: expected ',' or '}' after property-value 
             pair in object literal
SyntaxError: JSON.parse: property names must be double-quoted strings
SyntaxError: JSON.parse: expected property name or '}'
SyntaxError: JSON.parse: unexpected character
SyntaxError: JSON.parse: unexpected non-whitespace character after 
             JSON data
SyntaxError: JSON.parse Error: Invalid character at position {0} 
             (Edge)
SyntaxError

{"Prop_1" : "Val_1"}
Unexpected token } in JSON at position 2

ValidateJavaScript — Find & Fix JavaScript Errors

No JavaScript Errors

Javascript – Babel ES-Lint parse errors in imported module. Cannot find module ‘./parse-with-patch’ – Code Utility

{
  "parser": "babel-eslint",
  "extends": ["airbnb", "prettier"],
  "plugins": ["prettier", "flowtype"],
  "rules": {
    "prettier/prettier": ["error"],
    "no-unused-expressions": 0,
    "react/jsx-filename-extension": 0,
    "class-methods-use-this": 0,
    "default-case": 0,
    "import/no-unresolved": 0,
    "react/prefer-stateless-function": 0,
    "import/no-named-as-default": 0
  },
  "parserOptions": {
    "sourceType": "module",
    "import/extensions": [".jsx", ".js"],
    "allowImportExportEverywhere": true
  }
}
"devDependencies": {
    "@babel/plugin-transform-runtime": "^7.1.0",
    "babel-eslint": "^10.0.1",
    "eslint": "^5.7.0",
    "eslint-config-airbnb": "^17.1.0",
    "eslint-config-prettier": "^3.1.0",
    "eslint-plugin-flowtype": "^2.34.1",
    "eslint-plugin-import": "^2.14.0",
    "eslint-plugin-jsx-a11y": "^6.0.2",
    "eslint-plugin-prettier": "^3.0.0",
    "eslint-plugin-react": "^7.11.1",
    "flow-bin": "0.78.0",
    "jest": "23.5.0",
    "jest-react-native": "18.0.0",
    "metro-react-native-babel-preset": "^0.45.0",
    "prettier": "1.14.1",
    "react-native-debugger-open": "0.3.17",
    "react-test-renderer": "16.0.0-alpha.12",
    "schedule": "0.4.0",
    "semver": "5.5.0"
  },
"rules": {
    "import/no-named-as-default": 0
}

SyntaxError: JSON.parse: bad parsing

SyntaxError: JSON.parse: unterminated string literal
SyntaxError: JSON.parse: bad control character in string literal
SyntaxError: JSON.parse: bad character in string literal
SyntaxError: JSON.parse: bad Unicode escape
SyntaxError: JSON.parse: bad escape character
SyntaxError: JSON.parse: unterminated string
SyntaxError: JSON.parse: no number after minus sign
SyntaxError: JSON.parse: unexpected non-digit
SyntaxError: JSON.parse: missing digits after decimal point
SyntaxError: JSON.parse: unterminated fractional number
SyntaxError: JSON.parse: missing digits after exponent indicator
SyntaxError: JSON.parse: missing digits after exponent sign
SyntaxError: JSON.parse: exponent part is missing a number
SyntaxError: JSON.parse: unexpected end of data
SyntaxError: JSON.parse: unexpected keyword
SyntaxError: JSON.parse: unexpected character
SyntaxError: JSON.parse: end of data while reading object contents
SyntaxError: JSON.parse: expected property name or '}'
SyntaxError: JSON.parse: end of data when ',' or ']' was expected
SyntaxError: JSON.parse: expected ',' or ']' after array element
SyntaxError: JSON.parse: end of data when property name was expected
SyntaxError: JSON.parse: expected double-quoted property name
SyntaxError: JSON.parse: end of data after property name when ':' was expected
SyntaxError: JSON.parse: expected ':' after property name in object
SyntaxError: JSON.parse: end of data after property value in object
SyntaxError: JSON.parse: expected ',' or '}' after property value in object
SyntaxError: JSON.parse: expected ',' or '}' after property-value pair in object literal
SyntaxError: JSON.parse: property names must be double-quoted strings
SyntaxError: JSON.parse: expected property name or '}'
SyntaxError: JSON.parse: unexpected character
SyntaxError: JSON.parse: unexpected non-whitespace character after JSON data
SyntaxError: JSON.parse Error: Invalid character at position {0} (Edge)
JSON.parse('[1, 2, 3, 4,]');
JSON.parse('{"foo": 1,}');
// SyntaxError JSON.parse: unexpected character
// at line 1 column 14 of the JSON data
JSON.parse('[1, 2, 3, 4]');
JSON.parse('{"foo": 1}');
JSON.parse("{'foo': 1}");
// SyntaxError: JSON.parse: expected property name or '}'
// at line 1 column 2 of the JSON data
JSON.parse('{"foo": 1}');
JSON.parse('{"foo": 01}');
// SyntaxError: JSON.parse: expected ',' or '}' after property value
// in object at line 1 column 2 of the JSON data

JSON.parse('{"foo": 1.}');
// SyntaxError: JSON.parse: unterminated fractional number
// at line 1 column 2 of the JSON data
JSON.parse('{"foo": 1}');
JSON.parse('{"foo": 1.0}');
SyntaxError: JSON.parse: unterminated string literalnSyntaxError: JSON.parse: bad control character in string literalnSyntaxError: JSON.parse: bad character in string literalnSyntaxError: JSON.parse: bad Unicode escapenSyntaxError: JSON.parse: bad escape characternSyntaxError: JSON.parse: unterminated stringnSyntaxError: JSON.parse: no number after minus signnSyntaxError: JSON.parse: unexpected non-digitnSyntaxError: JSON.parse: missing digits after decimal pointnSyntaxError: JSON.parse: unterminated fractional numbernSyntaxError: JSON.parse: missing digits after exponent indicatornSyntaxError: JSON.parse: missing digits after exponent signnSyntaxError: JSON.parse: exponent part is missing a numbernSyntaxError: JSON.parse: unexpected end of datanSyntaxError: JSON.parse: unexpected keywordnSyntaxError: JSON.parse: unexpected characternSyntaxError: JSON.parse: end of data while reading object contentsnSyntaxError: JSON.parse: expected property name or '}'nSyntaxError: JSON.parse: end of data when ',' or ']' was expectednSyntaxError: JSON.parse: expected ',' or ']' after array elementnSyntaxError: JSON.parse: end of data when property name was expectednSyntaxError: JSON.parse: expected double-quoted property namenSyntaxError: JSON.parse: end of data after property name when ':' was expectednSyntaxError: JSON.parse: expected ':' after property name in objectnSyntaxError: JSON.parse: end of data after property value in objectnSyntaxError: JSON.parse: expected ',' or '}' after property value in objectnSyntaxError: JSON.parse: expected ',' or '}' after property-value pair in object literalnSyntaxError: JSON.parse: property names must be double-quoted stringsnSyntaxError: JSON.parse: expected property name or '}'nSyntaxError: JSON.parse: unexpected characternSyntaxError: JSON.parse: unexpected non-whitespace character after JSON datanSyntaxError: JSON.parse Error: Invalid character at position {0} (Edge)n
JSON.parse('[1, 2, 3, 4,]');nJSON.parse('{"foo": 1,}');n// SyntaxError JSON.parse: unexpected charactern// at line 1 column 14 of the JSON datan
JSON.parse('[1, 2, 3, 4]');nJSON.parse('{"foo": 1}');n
JSON.parse("{'foo': 1}");n// SyntaxError: JSON.parse: expected property name or '}'n// at line 1 column 2 of the JSON datan
JSON.parse('{"foo": 1}');n
JSON.parse('{"foo": 01}');n// SyntaxError: JSON.parse: expected ',' or '}' after property valuen// in object at line 1 column 2 of the JSON datannJSON.parse('{"foo": 1.}');n// SyntaxError: JSON.parse: unterminated fractional numbern// at line 1 column 2 of the JSON datan
JSON.parse('{"foo": 1}');nJSON.parse('{"foo": 1.0}');n

JavaScript — Errors & Exceptions Handling

<script type = "text/javascript">
   <!--
      window.print(;
   //-->
</script>
<script type = "text/javascript">
   <!--
      window.printme();
   //-->
</script>
<script type = "text/javascript">
   <!--
      try {
         // Code to run
         [break;]
      } 
      
      catch ( e ) {
         // Code to run if an exception occurs
         [break;]
      }
      
      [ finally {
         // Code that is always executed regardless of 
         // an exception occurring
      }]
   //-->
</script>
<html>
   <head>      
      <script type = "text/javascript">
         <!--
            function myFunc() {
               var a = 100;
               alert("Value of variable a is : " + a );
            }
         //-->
      </script>      
   </head>
   
   <body>
      <p>Click the following to see the result:</p>
      
      <form>
         <input type = "button" value = "Click Me" onclick = "myFunc();" />
      </form>      
   </body>
</html>
<html>
   <head>
      
      <script type = "text/javascript">
         <!--
            function myFunc() {
               var a = 100;
               try {
                  alert("Value of variable a is : " + a );
               } 
               catch ( e ) {
                  alert("Error: " + e.description );
               }
            }
         //-->
      </script>
      
   </head>
   <body>
      <p>Click the following to see the result:</p>
      
      <form>
         <input type = "button" value = "Click Me" onclick = "myFunc();" />
      </form>
      
   </body>
</html>
<html>
   <head>
      
      <script type = "text/javascript">
         <!--
            function myFunc() {
               var a = 100;
               
               try {
                  alert("Value of variable a is : " + a );
               }
               catch ( e ) {
                  alert("Error: " + e.description );
               }
               finally {
                  alert("Finally block will always execute!" );
               }
            }
         //-->
      </script>
      
   </head>
   <body>
      <p>Click the following to see the result:</p>
      
      <form>
         <input type = "button" value = "Click Me" onclick = "myFunc();" />
      </form>
      
   </body>
</html>
<html>
   <head>
      
      <script type = "text/javascript">
         <!--
            function myFunc() {
               var a = 100;
               var b = 0;
               
               try {
                  if ( b == 0 ) {
                     throw( "Divide by zero error." ); 
                  } else {
                     var c = a / b;
                  }
               }
               catch ( e ) {
                  alert("Error: " + e );
               }
            }
         //-->
      </script>
      
   </head>
   <body>
      <p>Click the following to see the result:</p>
      
      <form>
         <input type = "button" value = "Click Me" onclick = "myFunc();" />
      </form>
      
   </body>
</html>
<html>
   <head>
      
      <script type = "text/javascript">
         <!--
            window.onerror = function () {
               alert("An error occurred.");
            }
         //-->
      </script>
      
   </head>
   <body>
      <p>Click the following to see the result:</p>
      
      <form>
         <input type = "button" value = "Click Me" onclick = "myFunc();" />
      </form>
      
   </body>
</html>
<html>
   <head>
   
      <script type = "text/javascript">
         <!--
            window.onerror = function (msg, url, line) {
               alert("Message : " + msg );
               alert("url : " + url );
               alert("Line number : " + line );
            }
         //-->
      </script>
      
   </head>
   <body>
      <p>Click the following to see the result:</p>
      
      <form>
         <input type = "button" value = "Click Me" onclick = "myFunc();" />
      </form>
      
   </body>
</html>
<img src="myimage.gif" onerror="alert('An error occurred loading the image.')" />

Getting Started

const Parse = require('parse');
// In a node.js environment
const Parse = require('parse/node');
// ES6 Minimized
import Parse from 'parse/dist/parse.min.js';
// In a React Native application
const Parse = require('parse/react-native.js');
//Get your favorite AsyncStorage handler with import (ES6) or require
import { AsyncStorage } from 'react-native'; 

//Before using the SDK...
Parse.setAsyncStorage(AsyncStorage);
Parse.initialize("YOUR_APP_ID", "YOUR_JAVASCRIPT_KEY");
//javascriptKey is required only if you have it on server.

Parse.serverURL = 'http://YOUR_PARSE_SERVER:1337/parse'
Parse.initialize("YOUR_APP_ID", "YOUR_JAVASCRIPT_KEY", "YOUR_MASTERKEY");
//javascriptKey is required only if you have it on server.

Parse.serverURL = 'http://YOUR_PARSE_SERVER:1337/parse'
score: 1337, playerName: "Sean Plott", cheatMode: false
// Simple syntax to create a new subclass of Parse.Object.
const GameScore = Parse.Object.extend("GameScore");

// Create a new instance of that class.
const gameScore = new GameScore();

// Alternatively, you can use the typical Backbone syntax.
const Achievement = Parse.Object.extend({
  className: "Achievement"
});
// A complex subclass of Parse.Object
const Monster = Parse.Object.extend("Monster", {
  // Instance methods
  hasSuperHumanStrength: function () {
    return this.get("strength") > 18;
  },
  // Instance properties go in an initialize method
  initialize: function (attrs, options) {
    this.sound = "Rawr"
  }
}, {
  // Class methods
  spawn: function(strength) {
    const monster = new Monster();
    monster.set("strength", strength);
    return monster;
  }
});

const monster = Monster.spawn(200);
alert(monster.get('strength'));  // Displays 200.
alert(monster.sound); // Displays Rawr.
class Monster extends Parse.Object {
  constructor() {
    // Pass the ClassName to the Parse.Object constructor
    super('Monster');
    // All other initialization
    this.sound = 'Rawr';
  }

  hasSuperHumanStrength() {
    return this.get('strength') > 18;
  }

  static spawn(strength) {
    const monster = new Monster();
    monster.set('strength', strength);
    return monster;
  }
}
// After specifying the Monster subclass...
Parse.Object.registerSubclass('Monster', Monster);
class CustomUser extends Parse.User {
  constructor(attributes) {
    super(attributes);
  }

  doSomething() {
    return 5;
  }
}
Parse.Object.registerSubclass('_User', CustomUser);
const customUser = new CustomUser({ foo: 'bar' });
customUser.setUsername('username');
customUser.setPassword('password');
customUser.signUp().then((user) => {
  // user is an instance of CustomUser
  user.doSomething(); // return 5
  user.get('foo');    // return 'bar'
});
const GameScore = Parse.Object.extend("GameScore");
const gameScore = new GameScore();

gameScore.set("score", 1337);
gameScore.set("playerName", "Sean Plott");
gameScore.set("cheatMode", false);

gameScore.save()
.then((gameScore) => {
  // Execute any logic that should take place after the object is saved.
  alert('New object created with objectId: ' + gameScore.id);
}, (error) => {
  // Execute any logic that should take place if the save fails.
  // error is a Parse.Error with an error code and message.
  alert('Failed to create new object, with error code: ' + error.message);
});
objectId: "xWMyZ4YEGZ", score: 1337, playerName: "Sean Plott", cheatMode: false,
createdAt:"2011-06-10T18:33:42Z", updatedAt:"2011-06-10T18:33:42Z"
const GameScore = Parse.Object.extend("GameScore");
const gameScore = new GameScore();

gameScore.save({
  score: 1337,
  playerName: "Sean Plott",
  cheatMode: false
})
.then((gameScore) => {
  // The object was saved successfully.
}, (error) => {
  // The save failed.
  // error is a Parse.Error with an error code and message.
});
const Child = Parse.Object.extend("Child");
const child = new Child();

const Parent = Parse.Object.extend("Parent");
const parent = new Parent();

parent.save({ child: child });
// Automatically the object Child is created on the server
// just before saving the Parent
const TeamMember = Parse.Object.extend("TeamMember");
const teamMember = new TeamMember();
teamMember.set('ownerAccount', ownerAccount);   // Suppose `ownerAccount` has been created earlier.

teamMember.save(null, { cascadeSave: false });
// Will save `teamMember` wihout attempting to save or modify `ownerAccount`

const TeamMember = Parse.Object.extend("TeamMember");
const teamMember = new TeamMember();
teamMember.set("team", "A");

const context = { notifyTeam: false };
await teamMember.save(null, { context: context });
Parse.Cloud.afterSave("TeamMember", async (req) => {
  const notifyTeam = req.context.notifyTeam;
  if (notifyTeam) {
    // Notify team about new member.
  }
});
const GameScore = Parse.Object.extend("GameScore");
const query = new Parse.Query(GameScore);
query.get("xWMyZ4YEGZ")
.then((gameScore) => {
  // The object was retrieved successfully.
}, (error) => {
  // The object was not retrieved successfully.
  // error is a Parse.Error with an error code and message.
});
const score = gameScore.get("score");
const playerName = gameScore.get("playerName");
const cheatMode = gameScore.get("cheatMode");
const { score, playerName, cheatMode } = result.attributes;
const objectId = gameScore.id;
const updatedAt = gameScore.updatedAt;
const createdAt = gameScore.createdAt;
const acl = gameScore.getACL();
myObject.fetch().then((myObject) => {
  // The object was refreshed successfully.
}, (error) => {
  // The object was not refreshed successfully.
  // error is a Parse.Error with an error code and message.
});
if (!myObject.isDataAvailable()) {
  await myObject.fetch();
}
// Create the object.
const GameScore = Parse.Object.extend("GameScore");
const gameScore = new GameScore();

gameScore.set("score", 1337);
gameScore.set("playerName", "Sean Plott");
gameScore.set("cheatMode", false);
gameScore.set("skills", ["pwnage", "flying"]);

gameScore.save().then((gameScore) => {
  // Now let's update it with some new data. In this case, only cheatMode and score
  // will get sent to the cloud. playerName hasn't changed.
  gameScore.set("cheatMode", true);
  gameScore.set("score", 1338);
  return gameScore.save();
});
gameScore.increment("score");
gameScore.save();
gameScore.addUnique("skills", "flying");
gameScore.addUnique("skills", "kungfu");
gameScore.save();
myObject.destroy().then((myObject) => {
  // The object was deleted from the Parse Cloud.
}, (error) => {
  // The delete failed.
  // error is a Parse.Error with an error code and message.
});
// After this, the playerName field will be empty
myObject.unset("playerName");

// Saves the field deletion to the Parse Cloud.
// If the object's field is an array, call save() after every unset() operation.
myObject.save();
// Declare the types.
const Post = Parse.Object.extend("Post");
const Comment = Parse.Object.extend("Comment");

// Create the post
const myPost = new Post();
myPost.set("title", "I'm Hungry");
myPost.set("content", "Where should we go for lunch?");

// Create the comment
const myComment = new Comment();
myComment.set("content", "Let's do Sushirrito.");

// Add the post as a value in the comment
myComment.set("parent", myPost);

// This will save both myPost and myComment
myComment.save();
const post = new Post();
post.id = "1zEcyElZ80";

myComment.set("parent", post);
const post = fetchedComment.get("parent");
await post.fetch();
const title = post.get("title");
const user = Parse.User.current();
const relation = user.relation("likes");
relation.add(post);
user.save();
relation.remove(post);
user.save();
relation.remove(post1);
relation.remove(post2);
user.save();
relation.add([post1, post2, post3]);
user.save();
relation.query().find({
  success: function(list) {
    // list contains the posts that the current user likes.
  }
});
const query = relation.query();
query.equalTo("title", "I'm Hungry");
query.find({
  success:function(list) {
    // list contains post liked by the current user which have the title "I'm Hungry".
  }
});
const number = 42;
const bool = false;
const string = "the number is " + number;
const date = new Date();
const array = [string, number];
const object = { number: number, string: string };
const pointer = MyClassName.createWithoutData(objectId);

const BigObject = Parse.Object.extend("BigObject");
const bigObject = new BigObject();
bigObject.set("myNumber", number);
bigObject.set("myBool", bool);
bigObject.set("myString", string);
bigObject.set("myDate", date);
bigObject.set("myArray", array);
bigObject.set("myObject", object);
bigObject.set("anyKey", null); // this value can only be saved to an existing key
bigObject.set("myPointerKey", pointer); // shows up as Pointer &lt;MyClassName&gt; in the Data Browser
bigObject.save();
const GameScore = Parse.Object.extend("GameScore");
const query = new Parse.Query(GameScore);
query.equalTo("playerName", "Dan Stemkoski");
const results = await query.find();
alert("Successfully retrieved " + results.length + " scores.");
// Do something with the returned Parse.Object values
for (let i = 0; i < results.length; i++) {
  const object = results[i];
  alert(object.id + ' - ' + object.get('playerName'));
}
query.notEqualTo("playerName", "Michael Yabuti");
query.notEqualTo("playerName", "Michael Yabuti");
query.greaterThan("playerAge", 18);
query.limit(10); // limit to at most 10 results
const GameScore = Parse.Object.extend("GameScore");
const query = new Parse.Query(GameScore);
query.equalTo("playerEmail", "[email protected]");
const object = await query.first();
query.skip(10); // skip the first 10 results
const GameScore = Parse.Object.extend("GameScore");
const query = new Parse.Query(GameScore);

query.limit(25);

const results = await query.find(); // [ GameScore, GameScore, ...]

// to include count:
query.withCount();
const response = await query.find(); // { results: [ GameScore, ... ], count: 200 }
// Sorts the results in ascending order by the score field
query.ascending("score");

// Sorts the results in descending order by the score field
query.descending("score");
// Restricts to wins < 50
query.lessThan("wins", 50);

// Restricts to wins <= 50
query.lessThanOrEqualTo("wins", 50);

// Restricts to wins > 50
query.greaterThan("wins", 50);

// Restricts to wins >= 50
query.greaterThanOrEqualTo("wins", 50);
// Finds scores from any of Jonathan, Dario, or Shawn
query.containedIn("playerName",
                  ["Jonathan Walsh", "Dario Wunsch", "Shawn Simon"]);
// Finds scores from anyone who is neither Jonathan, Dario, nor Shawn
query.notContainedIn("playerName",
                     ["Jonathan Walsh", "Dario Wunsch", "Shawn Simon"]);
// Finds objects that have the score set
query.exists("score");

// Finds objects that don't have the score set
query.doesNotExist("score");
const Team = Parse.Object.extend("Team");
const teamQuery = new Parse.Query(Team);
teamQuery.greaterThan("winPct", 0.5);
const userQuery = new Parse.Query(Parse.User);
userQuery.matchesKeyInQuery("hometown", "city", teamQuery);
// results has the list of users with a hometown team with a winning record
const results = await userQuery.find();
const losingUserQuery = new Parse.Query(Parse.User);
losingUserQuery.doesNotMatchKeyInQuery("hometown", "city", teamQuery);
// results has the list of users with a hometown team with a losing record
const results = await losingUserQuery.find();
const rolesOfTypeX = new Parse.Query('Role');
rolesOfTypeX.equalTo('type', 'x');

const groupsWithRoleX = new Parse.Query('Group');
groupsWithRoleX.matchesKeyInQuery('objectId', 'belongsTo.objectId', rolesOfTypeX);
groupsWithRoleX.find().then(function(results) {
   // results has the list of groups with role x
});
const GameScore = Parse.Object.extend("GameScore");
const query = new Parse.Query(GameScore);
query.select("score", "playerName");
query.find().then(function(results) {
  // each of results will only have the selected fields available.
});
const GameScore = Parse.Object.extend("GameScore");
const query = new Parse.Query(GameScore);
query.exclude("playerName");
query.find().then(function(results) {
  // Now each result will have all fields except `playerName`
});
query.first().then(function(result) {
  // only the selected fields of the object will now be available here.
  return result.fetch();
}).then(function(result) {
  // all fields of the object will now be available here.
});
// Find objects where the array in arrayKey contains 2.
query.equalTo("arrayKey", 2);
// Find objects where the array in arrayKey contains all of the elements 2, 3, and 4.
query.containsAll("arrayKey", [2, 3, 4]);
// Finds barbecue sauces that start with "Big Daddy's".
const query = new Parse.Query(BarbecueSauce);
query.startsWith("name", "Big Daddy's");
const query = new Parse.Query(BarbecueSauce);
query.fullText('name', 'bbq');
// You can sort by weight / rank. ascending() and select()
const query = new Parse.Query(BarbecueSauce);
query.fullText('name', 'bbq');
query.ascending('$score');
query.select('$score');
query.find()
  .then(function(results) {
    // results contains a weight / rank in result.get('score')
  })
  .catch(function(error) {
    // There was an error.
  });
// Assume Parse.Object myPost was previously created.
const query = new Parse.Query(Comment);
query.equalTo("post", myPost);
// comments now contains the comments for myPost
const comments = await query.find();
const Post = Parse.Object.extend("Post");
const Comment = Parse.Object.extend("Comment");
const innerQuery = new Parse.Query(Post);
innerQuery.exists("image");
const query = new Parse.Query(Comment);
query.matchesQuery("post", innerQuery);
// comments now contains the comments for posts with images.
const comments = await query.find();
const Post = Parse.Object.extend("Post");
const Comment = Parse.Object.extend("Comment");
const innerQuery = new Parse.Query(Post);
innerQuery.exists("image");
const query = new Parse.Query(Comment);
query.doesNotMatchQuery("post", innerQuery);
// comments now contains the comments for posts without images.
const comments = await query.find();
const post = new Post();
post.id = "1zEcyElZ80";
query.equalTo("post", post);
const query = new Parse.Query(Comment);

// Retrieve the most recent ones
query.descending("createdAt");

// Only retrieve the last ten
query.limit(10);

// Include the post data with each comment
query.include("post");

// Comments now contains the last ten comments, and the "post" field
const comments = await query.find();
// has been populated. For example:
for (let i = 0; i < comments.length; i++) {
  // This does not require a network access.
  const post = comments[i].get("post");
}
query.include(["post.author"]);
const GameScore = Parse.Object.extend("GameScore");
const query = new Parse.Query(GameScore);
query.equalTo("playerName", "Sean Plott");
const count = await query.count();
alert("Sean has played " + count + " games");
const lotsOfWins = new Parse.Query("Player");
lotsOfWins.greaterThan("wins", 150);

const fewWins = new Parse.Query("Player");
fewWins.lessThan("wins", 5);

const mainQuery = Parse.Query.or(lotsOfWins, fewWins);
mainQuery.find()
  .then(function(results) {
    // results contains a list of players that either have won a lot of games or won only a few games.
  })
  .catch(function(error) {
    // There was an error.
  });
const query = new Parse.Query("User");
query.greaterThan("age", 18);
query.greaterThan("friends", 0);
query.find()
  .then(function(results) {
    // results contains a list of users both older than 18 and having friends.
  })
  .catch(function(error) {
    // There was an error.
  });
const age16Query = new Parse.Query("User");
age16Query.equalTo("age", 16);

const age18Query = new Parse.Query("User");
age18Query.equalTo("age", 18);

const friends0Query = new Parse.Query("User");
friends0Query.equalTo("friends", 0);

const friends2Query = new Parse.Query("User");
friends2Query.greaterThan("friends", 2);

const mainQuery = Parse.Query.and(
  Parse.Query.or(age16Query, age18Query),
  Parse.Query.or(friends0Query, friends2Query)
);
mainQuery.find()
  .then(function(results) {
    // results contains a list of users in the age of 16 or 18 who have either no friends or at least 2 friends
    // results: (age 16 or 18) and (0 or >2 friends)
  })
  .catch(function(error) {
    // There was an error.
  });
const pipelineObject = {
  group: { objectId: '$name' }
 };

const pipelineArray = [
  { group: { objectId: '$name' } }
];
// score is the field. $ before score lets the database know this is a field
const pipeline = [
  { group: { objectId: '$score' } }
];
const query = new Parse.Query("User");
query.aggregate(pipeline)
  .then(function(results) {
    // results contains unique score values
  })
  .catch(function(error) {
    // There was an error.
  });
// total will be a newly created field to hold the sum of score field
const pipeline = [
  { group: { objectId: null, total: { $sum: '$score' } } }
];
const query = new Parse.Query("User");
query.aggregate(pipeline)
  .then(function(results) {
    // results contains sum of score field and stores it in results[0].total
  })
  .catch(function(error) {
    // There was an error.
  });
const pipeline = [
  { project: { name: 1 } }
];
const query = new Parse.Query("User");
query.aggregate(pipeline)
  .then(function(results) {
    // results contains only name field
  })
  .catch(function(error) {
    // There was an error.
  });
const pipeline = [
  { match: { name: 'BBQ' } }
];
const query = new Parse.Query("User");
query.aggregate(pipeline)
  .then(function(results) {
    // results contains name that matches 'BBQ'
  })
  .catch(function(error) {
    // There was an error.
  });
const pipeline = [
  { match: { score: { $gt: 15 } } }
];
const query = new Parse.Query("User");
query.aggregate(pipeline)
  .then(function(results) {
    // results contains score greater than 15
  })
  .catch(function(error) {
    // There was an error.
  });
const query = new Parse.Query("User");
query.distinct("age")
  .then(function(results) {
    // results contains unique age
  })
  .catch(function(error) {
    // There was an error.
  });
const query = new Parse.Query("User");
query.equalTo("name", "foo");
query.distinct("age")
  .then(function(results) {
    // results contains unique age where name is foo
  })
  .catch(function(error) {
    // There was an error.
  });
query.readPreference(
  'SECONDARY',
  'SECONDARY_PREFERRED',
  'NEAREST'
);
let query = new Parse.Query('Game');
let subscription = await query.subscribe();
subscription.on('open', () => {
 console.log('subscription opened');
});
subscription.on('create', (object) => {
  console.log('object created');
});
subscription.on('update', (object) => {
  console.log('object updated');
});
subscription.on('enter', (object) => {
  console.log('object entered');
});
subscription.on('leave', (object) => {
  console.log('object left');
});
subscription.on('delete', (object) => {
  console.log('object deleted');
});
subscription.on('close', () => {
  console.log('subscription closed');
});
subscription.unsubscribe();
Parse.LiveQuery.close();
Parse.liveQueryServerURL = 'ws://XXXX'
Parse.LiveQuery.on('open', () => {
  console.log('socket connection established');
});
Parse.LiveQuery.on('close', () => {
  console.log('socket connection closed');
});
Parse.LiveQuery.on('error', (error) => {
  console.log(error);
});
let Parse = require('parse/node');
let LiveQueryClient = Parse.LiveQueryClient;
let client = new LiveQueryClient({
  applicationId: '',
  serverURL: '',
  javascriptKey: '',
  masterKey: ''
});
client.open();
let query = new Parse.Query('Game');
let subscription = client.subscribe(query, sessionToken); 
client.unsubscribe(subscription); 
client.close();
client.on('open', () => {
  console.log('connection opened');
});
client.on('close', () => {
  console.log('connection closed');
});
client.on('error', (error) => {
  console.log('connection error');
});
const user = new Parse.User();
user.set("username", "my name");
user.set("password", "my pass");
user.set("email", "[email protected]");

// other fields can be set just like with Parse.Object
user.set("phone", "415-392-0202");
try {
  await user.signUp();
  // Hooray! Let them use the app now.
} catch (error) {
  // Show the error message somewhere and let the user try again.
  alert("Error: " + error.code + " " + error.message);
}
const user = await Parse.User.logIn("myname", "mypass");
// Do stuff after successful login.
const user = await Parse.User.logIn("myname", "mypass", { usePost: true });
// Do stuff after successful login.
const currentUser = Parse.User.current();
if (currentUser) {
    // do stuff with the user
} else {
    // show the signup or login page
}
Parse.User.currentAsync().then(function(user) {
    // do stuff with your user
});
Parse.User.logOut().then(() => {
  const currentUser = Parse.User.current();  // this will now be null
});
Parse.User.become("session-token-here").then(function (user) {
  // The current user is now set to user.
}, function (error) {
  // The token could not be validated.
});
const user = await Parse.User.logIn("my_username", "my_password");
user.set("username", "my_new_username");
await user.save();
// This succeeds, since the user was authenticated on the device

// Get the user from a non-authenticated method
const query = new Parse.Query(Parse.User);
const userAgain = await query.get(user.objectId);
userAgain.set("username", "another_username");
await userAgain.save().catch(error => {
  // This will error, since the Parse.User is not authenticated
});
Parse.enableEncryptedUser();
Parse.secret = 'my Secrey Key';

const Note = Parse.Object.extend("Note");
const privateNote = new Note();
privateNote.set("content", "This note is private!");
privateNote.setACL(new Parse.ACL(Parse.User.current()));
privateNote.save();
const Message = Parse.Object.extend("Message");
const groupMessage = new Message();
const groupACL = new Parse.ACL();

// userList is an array with the users we are sending this message to.
for (let i = 0; i < userList.length; i++) {
  groupACL.setReadAccess(userList[i], true);
  groupACL.setWriteAccess(userList[i], true);
}

groupMessage.setACL(groupACL);
groupMessage.save();
const publicPost = new Post();
const postACL = new Parse.ACL(Parse.User.current());
postACL.setPublicReadAccess(true);
publicPost.setACL(postACL);
publicPost.save();
Parse.User.requestPasswordReset("[email protected]")
.then(() => {
  // Password reset request was sent successfully
}).catch((error) => {
  // Show the error message somewhere
  alert("Error: " + error.code + " " + error.message);
});
const query = new Parse.Query(Parse.User);
query.equalTo("gender", "female");  // find all the women
const women = await query.find();
const user = Parse.User.current();

// Make a new post
const Post = Parse.Object.extend("Post");
const post = new Post();
post.set("title", "My New Post");
post.set("body", "This is some great content.");
post.set("user", user);
await post.save();
// Find all posts by the current user
const query = new Parse.Query(Post);
query.equalTo("user", user);
const userPosts = await query.find();
// userPosts contains all of the posts by the current user.
});
<script>
  // Initialize Parse
  Parse.initialize("$PARSE_APPLICATION_ID", "$PARSE_JAVASCRIPT_KEY");
  Parse.serverURL = 'http://YOUR_PARSE_SERVER:1337/parse';

  window.fbAsyncInit = function() {
    Parse.FacebookUtils.init({
      appId      : '{facebook-app-id}', // Facebook App ID
      status     : true,  // check Facebook Login status
      cookie     : true,  // enable cookies to allow Parse to access the session
      xfbml      : true,  // initialize Facebook social plugins on the page
      version    : 'v2.3' // point to the latest Facebook Graph API version
    });
    // Run code after the Facebook SDK is loaded.
    // ...
  };

  // Load Facebook SDK
  (function(d, s, id){
    var js, fjs = d.getElementsByTagName(s)[0];
    if (d.getElementById(id)) {return;}
    js = d.createElement(s); js.id = id;
    js.src = "//connect.facebook.net/en_US/sdk.js";
    fjs.parentNode.insertBefore(js, fjs);
  }(document, 'script', 'facebook-jssdk'));
</script>
try {
  const users = await Parse.FacebookUtils.logIn();
  if (!user.existed()) {
    alert("User signed up and logged in through Facebook!");
  } else {
    alert("User logged in through Facebook!");
  }
} catch(error) {
  alert("User cancelled the Facebook login or did not fully authorize.");
}
const user = await Parse.FacebookUtils.logIn("user_likes,email");
if (!Parse.FacebookUtils.isLinked(user)) {
  try  {
    await Parse.FacebookUtils.link(user);
    alert("Woohoo, user logged in with Facebook!");
  } catch(error) {}
    alert("User cancelled the Facebook login or ddid not fully authorize.");
  });
}
await Parse.FacebookUtils.unlink(user);
alert("The user is no longer associated with their Facebook account.");
const myAuthData = {
  id: '12345678'  // Required field. Used to uniquely identify the linked account.
};
const user = new Parse.User();
await user.linkWith('providerName', { authData: myAuthData });
Status: 200 OK
Location: https://YOUR.PARSE-SERVER.HERE/parse/users/uMz0YZeAqc
{
  "username": "Parse",
  "createdAt": "2012-02-28T23:49:36.353Z",
  "updatedAt": "2012-02-28T23:49:36.353Z",
  "objectId": "uMz0YZeAqc",
  "sessionToken": "r:samplei3l83eerhnln0ecxgy5",
  "authData": {
    "providerName": {
      "id": "12345678",
    }
  }
}
Status: 201 Created
Location: https://YOUR.PARSE-SERVER.HERE/parse/users/uMz0YZeAqc
{
  "username": "iwz8sna7sug28v4eyu7t89fij",
  "createdAt": "2012-02-28T23:49:36.353Z",
  "objectId": "uMz0YZeAqc",
  "sessionToken": "r:samplei3l83eerhnln0ecxgy5"
}
const myAuthData = {
  id: xzx5tt123,  // The id key is required in the authData-object. Otherwise Parse Server will throw the Error 252 'This authentication method is unsupported'.
  access: token
}

const user = await Parse.Query(Parse.User).get(userId);

await user.linkWith(
  'providerName',
  { authData: myAuthData },
  { useMasterKey: true }
);
const loggedIn = await Parse.User.logInWith('CustomAdapter', { authData: myAuthData});

// Don't require or import parse-server in this module.
// See this issue: https://github.com/parse-community/parse-server/issues/6467
function validateAuthData(authData, options) {
  return Promise.resolve({})
}

function validateAppId(appIds, authData, options) {
  return Promise.resolve({});
}

module.exports = {
  validateAppId,
  validateAuthData,
};
const CustomAuth = require('./CustomAuth');

const api = new ParseServer({
  ...
  auth: {
    myAuth: {
      module: CustomAuth,
      option1: 'hello',
      option2: 'world',
    }
  }
  ...
});
...
app.use('/parse', api);
const provider = {
  authenticate: (options) => {
    // Some code to get retrieve authData
    // If retrieved valid authData, call success function with the data
    options.success(this, {
      id: 1234
    });
    // You can also handle error
    // options.error(this, {});
  },
  restoreAuthentication() {
    return true;
  },

  getAuthType() {
    return 'myAuth';
  },

  getAuthData() {
    return {
      authData: {
        id: 1234,
      },
    };
  },
};
// Must register before linking
Parse.User._registerAuthenticationProvider(provider);
const user = new Parse.User();
user.setUsername('Alice');
user.setPassword('sekrit');
await user.signUp();
await user.linkWith(provider.getAuthType(), provider.getAuthData());
user._isLinked(provider); // true
// Unlink
await user._unlinkFrom(provider.getAuthType());
function handleParseError(err) {
  switch (err.code) {
    case Parse.Error.INVALID_SESSION_TOKEN:
      Parse.User.logOut();
      ... // If web browser, render a log in screen
      ... // If Express.js, redirect the user to the log in route
      break;

    ... // Other Parse API errors that you want to explicitly handle
  }
}

// For each API request, call the global error handler
query.find().then(function() {
  ...
}, function(err) {
  handleParseError(err);
});
// By specifying no write privileges for the ACL, we can ensure the role cannot be altered.
const roleACL = new Parse.ACL();
roleACL.setPublicReadAccess(true);
const role = new Parse.Role("Administrator", roleACL);
role.save();
const role = new Parse.Role(roleName, roleACL);
role.getUsers().add(usersToAddToRole);
role.getRoles().add(rolesToAddToRole);
role.save();
const moderators = /* Query for some Parse.Role */;
const wallPost = new Parse.Object("WallPost");
const postACL = new Parse.ACL();
postACL.setRoleWriteAccess(moderators, true);
wallPost.setACL(postACL);
wallPost.save();
const wallPost = new Parse.Object("WallPost");
const postACL = new Parse.ACL();
postACL.setRoleWriteAccess("Moderators", true);
wallPost.setACL(postACL);
wallPost.save();
const administrators = /* Your "Administrators" role */;
const moderators = /* Your "Moderators" role */;
moderators.getRoles().add(administrators);
moderators.save();
const base64 = "V29ya2luZyBhdCBQYXJzZSBpcyBncmVhdCE=";
const file = new Parse.File("myfile.txt", { base64: base64 });
const bytes = [ 0xBE, 0xEF, 0xCA, 0xFE ];
const file = new Parse.File("myfile.txt", bytes);
const file = new Parse.File("myfile.zzz", fileData, "image/png");
<input type="file" id="profilePhotoFileUpload">
const fileUploadControl = $("#profilePhotoFileUpload")[0];
if (fileUploadControl.files.length > 0) {
  const file = fileUploadControl.files[0];
  const name = "photo.jpg";

  const parseFile = new Parse.File(name, file);
}
parseFile.save().then(function() {
  // The file has been saved to Parse.
}, function(error) {
  // The file either could not be read, or could not be saved to Parse.
});
const request = require('request-promise');
const Parse = require('parse/node');

....

const options = {
  uri: 'https://bit.ly/2zD8fgm',
  resolveWithFullResponse: true,
  encoding: null, // <-- this is important for binary data like images.
};

request(options)
  .then((response) => {
    const data = Array.from(Buffer.from(response.body, 'binary'));
    const contentType = response.headers['content-type'];
    const file = new Parse.File('logo', data, contentType);
    return file.save();
  })
  .then((file => console.log(file.url())))
  .catch(console.error);
const jobApplication = new Parse.Object("JobApplication");
jobApplication.set("applicantName", "Joe Smith");
jobApplication.set("applicantResumeFile", parseFile);
jobApplication.save();
const profilePhoto = profile.get("photoFile");
$("profileImg")[0].src = profilePhoto.url();
Parse.Cloud.httpRequest({ url: profilePhoto.url() }).then(function(response) {
  // The file contents are in response.buffer.
});
const profilePhoto = profile.get("photoFile");
await profilePhoto.destroy({ useMasterKey: true });
// Init with metadata and tags
const metadata = { createdById: 'some-user-id' };
const tags = { groupId: 'some-group-id' };
const file = new Parse.File('myfile.zzz', fileData, 'image/png', metadata, tags);

// Add metadata and tags
const file = new Parse.File('myfile.zzz', fileData, 'image/png');
file.addMetadata('createdById', 'some-user-id');
file.addTag('groupId', 'some-group-id');
object.save({ key: value }, {
  success: function(object) {
    // the object was saved.
  },
  error: function(object, error) {
    // saving the object failed.
  }
});
object.save({ key: value }).then(
  function(object) {
    // the object was saved.
  },
  function(error) {
    // saving the object failed.
  });
Parse.User.logIn("user", "pass", {
  success: function(user) {
    query.find({
      success: function(results) {
        results[0].save({ key: value }, {
          success: function(result) {
            // the object was saved.
          }
        });
      }
    });
  }
});
Parse.User.logIn("user", "pass").then(function(user) {
  return query.find();
}).then(function(results) {
  return results[0].save({ key: value });
}).then(function(result) {
  // the object was saved.
});
obj.save().then(function(obj) {
  // the object was saved successfully.
}, function(error) {
  // the save failed.
});
const query = new Parse.Query("Student");
query.descending("gpa");
query.find().then(function(students) {
  students[0].set("valedictorian", true);
  return students[0].save();

}).then(function(valedictorian) {
  return query.find();

}).then(function(students) {
  students[1].set("salutatorian", true);
  return students[1].save();

}).then(function(salutatorian) {
  // Everything is done!

});
Parse.User.logIn("user", "pass", {
  success: function(user) {
    query.find({
      success: function(results) {
        results[0].save({ key: value }, {
          success: function(result) {
            // the object was saved.
          },
          error: function(result, error) {
            // An error occurred.
          }
        });
      },
      error: function(error) {
        // An error occurred.
      }
    });
  },
  error: function(user, error) {
    // An error occurred.
  }
});
Parse.User.logIn("user", "pass").then(function(user) {
  return query.find();
}).then(function(results) {
  return results[0].save({ key: value });
}).then(function(result) {
  // the object was saved.
}, function(error) {
  // there was some error.
});
const query = new Parse.Query("Student");
query.descending("gpa");
query.find().then(function(students) {
  students[0].set("valedictorian", true);
  // Force this callback to fail.
  return Parse.Promise.error("There was an error.");

}).then(function(valedictorian) {
  // Now this will be skipped.
  return query.find();

}).then(function(students) {
  // This will also be skipped.
  students[1].set("salutatorian", true);
  return students[1].save();
}, function(error) {
  // This error handler WILL be called. error will be "There was an error.".
  // Let's handle the error by returning a new promise.
  return Parse.Promise.as("Hello!");

}).then(function(hello) {
  // Everything is done!
}, function(error) {
  // This isn't called because the error was already handled.
});
const query = new Parse.Query("Comments");
query.equalTo("post", post);

query.find().then(function(results) {
  // Create a trivial resolved promise as a base case.
  let promise = Promise.resolve();
  results.forEach((result) =>  {
    // For each item, extend the promise with a function to delete it.
    promise = promise.then(() => {
      // Return a promise that will be resolved when the delete is finished.
      return result.destroy();
    });
  });
  return promise;

}).then(function() {
  // Every comment was deleted.
});
const query = new Parse.Query("Comments");
query.equalTo("post", post);

query.find().then(function(results) {
  // Collect one promise for each delete into an array.
  const promises = [];
  const promises = results.map((result) => {
    // Start this delete immediately and add its promise to the list.
    return result.destroy();
  });
  // Return a new promise that is resolved when all of the deletes are finished.
  return Promise.all(promises);

}).then(function() {
  // Every comment was deleted.
});
const delay = function(millis) {
  return new Promise((resolve) => {
    setTimeout(resolve, millis);
  });
};

delay(100).then(function() {
  // This ran after 100ms!
});
const point = new Parse.GeoPoint({latitude: 40.0, longitude: -30.0});
placeObject.set("location", point);
// User's location
const userGeoPoint = userObject.get("location");
// Create a query for places
const query = new Parse.Query(PlaceObject);
// Interested in locations near user.
query.near("location", userGeoPoint);
// Limit what could be a lot of points.
query.limit(10);
// Final list of objects
const placesObjects = await query.find();
const location = new Parse.GeoPoint(37.708813, -122.526398);
const distance = 5;
const sorted = true;

const query = new Parse.Query(PizzaPlaceObject);
query.withinKilometers("location", location, distance, sorted);
// Pizza places within 5km sorted by distance
const pizzaPlacesInSF = await query.find();
const location = new Parse.GeoPoint(37.708813, -122.526398);
const distance = 5;
const sorted = false;

const query = new Parse.Query(PizzaPlaceObject);
query.withinKilometers("location", location, distance, sorted);
query.descending("rating");
// Pizza places within 5km sorted by rating
const pizzaPlacesInSF = query.find();
const southwestOfSF = new Parse.GeoPoint(37.708813, -122.526398);
const northeastOfSF = new Parse.GeoPoint(37.822802, -122.373962);

const query = new Parse.Query(PizzaPlaceObject);
query.withinGeoBox("location", southwestOfSF, northeastOfSF);
const pizzaPlacesInSF = await query.find();
query.withinPolygon("location", [geoPoint1, geoPoint2, geoPoint3]);
const robjectsWithGeoPointInPolygon = await query.find();
const p1 = [[0,0], [0,1], [1,1], [1,0]];
const p2 = [[0,0], [0,2], [2,2], [2,0]];
const p3 = [[10,10], [10,15], [15,15], [15,10], [10,10]];

const polygon1 = new Parse.Polygon(p1);
const polygon2 = new Parse.Polygon(p2);
const polygon3 = new Parse.Polygon(p3);

const point = new Parse.GeoPoint(0.5, 0.5);
const query = new Parse.Query(TestObject);
query.polygonContains('polygon', point);
// objects contains polygon1 and polygon2
const results = await query.find();
const points = [[0,0], [0,1], [1,1], [1,0]];
const inside = new Parse.GeoPoint(0.5, 0.5);
const outside = new Parse.GeoPoint(10, 10);
const polygon = new Parse.Polygon(points);
// Returns True
polygon.containsPoint(inside);
// Returns False
polygon.containsPoint(outside);
import SecureLS from 'secure-ls';
const ls = new SecureLS({ isCompression: false });

Parse.enableLocalDatastore();
Parse.setLocalDatastoreController({
  fromPinWithName: name => ls.get(name),
  pinWithName: (name, objects) => ls.set(name, JSON.stringify(objects)),
  unPinWithName: name => ls.remove(name),
  getAllContents: () => {
    let data = {};
    ls.getAllKeys().forEach((key) => {
      const value = ls.get(key).data;
      data[key] = value.includes('{') ? JSON.parse(value) : value;
    })
    return data;
  },
  clear: () => ls.removeAll()
});
const GameScore = Parse.Object.extend("GameScore");
const gameScore = new GameScore();

await gameScore.save();
await gameScore.pin();
await Parse.Object.pinAll(listOfObjects);
const GameScore = Parse.Object.extend("GameScore");
const query = new Parse.Query(GameScore);
query.fromLocalDatastore();
const result = await query.get('xWMyZ4YE');
const GameScore = Parse.Object.extend("GameScore");
const query = new Parse.Query(GameScore);
query.equalTo('playerName', 'Joe Bob');
query.fromLocalDatastore();
const results = await query.find();
await gameScore.unPin();
await Parse.Object.unPinAll(listOfObjects);
await Parse.Object.unPinAllObjects();
// Add several objects with a label.
await Parse.Object.pinAllWithName('MyScores', listOfObjects);

// Add another object with the same label.
await anotherGameScore.pinWithName('MyScores');
await Parse.Object.unPinAllWithName('MyScores', listOfObjects);
await Parse.Object.unPinAllObjectsWithName('MyScores');
const GameScore = Parse.Object.extend("GameScore");
const query = new Parse.Query(GameScore);
query.equalTo("playerName", "foo");
const results = await query.find();
await Parse.Object.unPinAllObjectsWithName('HighScores');
await Parse.Object.pinAllWithName('HighScores', results);
const GameScore = Parse.Object.extend("GameScore");
const query = new Parse.Query(GameScore);
query.equalTo("playerName", "foo");
query.fromLocalDatastore();
const results = await query.find();
const LDS = await Parse.dumpLocalDatastore();
const gameScore = new GameScore();
await gameScore.save();
await gameScore.pin();
const query = new Parse.Query(GameScore);
query.equalTo('objectId', gameScore.id)
const subscription = query.subscribe();
subscription.on('update', (object) => {
  // Since object (gameScore) is pinned, LDS will update automatically
});
Parse.Push.send({
  channels: [ "Giants", "Mets" ],
  data: {
    alert: "The Giants won against the Mets 2-3."
  }
})
.then(function() {
  // Push was successful
}, function(error) {
  // Handle error
});
const query = new Parse.Query(Parse.Installation);
query.equalTo('injuryReports', true);

Parse.Push.send({
  where: query, // Set our Installation query
  data: {
    alert: "Willie Hayes injured by own pop fly."
  }
})
.then(function() {
  // Push was successful
}, function(error) {
  // Handle error
});
const query = new Parse.Query(Parse.Installation);
query.equalTo('channels', 'Giants'); // Set our channel
query.equalTo('scores', true);

Parse.Push.send({
  where: query,
  data: {
    alert: "Giants scored against the A's! It's now 2-2."
  }
})
.then(function() {
  // Push was successful
}, function(error) {
  // Handle error
});
// Find users near a given location
const userQuery = new Parse.Query(Parse.User);
userQuery.withinMiles("location", stadiumLocation, 1.0);

// Find devices associated with these users
const pushQuery = new Parse.Query(Parse.Installation);
pushQuery.matchesQuery('user', userQuery);

// Send push notification to query
Parse.Push.send({
  where: pushQuery,
  data: {
    alert: "Free hotdogs at the Parse concession stand!"
  }
})
.then(function() {
  // Push was successful
}, function(error) {
  // Handle error
});
Parse.Push.send({
  channels: [ "Mets" ],
  data: {
    alert: "The Mets scored! The game is now tied 1-1.",
    badge: "Increment",
    sound: "cheering.caf",
    title: "Mets Score!"
  }
})
.then(function() {
  // Push was successful
}, function(error) {
  // Handle error
});
const query = new Parse.Query(Parse.Installation);
query.equalTo('channels', 'Indians');
query.equalTo('injuryReports', true);

Parse.Push.send({
  where: query,
  data: {
    action: "com.example.UPDATE_STATUS"
    alert: "Ricky Vaughn was injured in last night's game!",
    name: "Vaughn",
    newsItem: "Man bites dog"
  }
})
.then(function() {
  // Push was successful
}, function(error) {
  // Handle error
});
const oneWeekAway = new Date(...);

Parse.Push.send({
  where: everyoneQuery,
  expiration_time: oneWeekAway,
  data: {
    alert: "Season tickets on sale until next week!"
  }
})
.then(function() {
  // Push was successful
}, function(error) {
  // Handle error
});
const oneDayAway = new Date(...);
const sixDaysAwayEpoch = (new Date(...)).getTime();

Parse.Push.send({
  push_time: oneDayAway,
  expiration_interval: sixDaysAwayEpoch,
  data: {
    alert: "Season tickets on sale until next week!"
  }
})
.then(function() {
  // Push was successful
}, function(error) {
  // Handle error
});
// Notification for Android users
const queryAndroid = new Parse.Query(Parse.Installation);
queryAndroid.equalTo('deviceType', 'android');

Parse.Push.send({
  where: queryAndroid,
  data: {
    alert: "Your suitcase has been filled with tiny robots!"
  }
});

// Notification for iOS users
const queryIOS = new Parse.Query(Parse.Installation);
queryIOS.equalTo('deviceType', 'ios');

Parse.Push.send({
  where: queryIOS,
  data: {
    alert: "Your suitcase has been filled with tiny apples!"
  }
});

// Notification for Windows 8 users
const queryWindows = new Parse.Query(Parse.Installation);
queryWindows.equalTo('deviceType', 'winrt');

Parse.Push.send({
  where: queryWindows,
  data: {
    alert: "Your suitcase has been filled with tiny glass!"
  }
});

// Notification for Windows Phone 8 users
const queryWindowsPhone = new Parse.Query(Parse.Installation);
queryWindowsPhone.equalTo('deviceType', 'winphone');

Parse.Push.send({
  where: queryWindowsPhone,
  data: {
    alert: "Your suitcase is very hip; very metro."
  }
});
const tomorrowDate = new Date(...);

const query = new Parse.Query(Parse.Installation);
query.equalTo('user', user);

Parse.Push.send({
  where: query,
  data: {
    alert: "You previously created a reminder for the game today"
  },
  push_time: tomorrowDate
})
.then(function() {
  // Push was successful
}, function(error) {
  // Handle error
});
Parse.Config.get().then(function(config) {
  const winningNumber = config.get("winningNumber");
  const message = "Yay! The number is " + winningNumber + "!";
  console.log(message);
}, function(error) {
  // Something went wrong (e.g. request timed out)
});
Parse.Config.save({
	welcomeMesssage : "Welcome to Parse",
	ageOfParse : 3,
	tags : ["parse","sdk","js"]
}).then(function(config) {
  console.log("Cool! Config was saved and fetched from the server.");
  
  const welcomeMessage = config.get("welcomeMessage");
  console.log("Welcome Message = " + welcomeMessage);
}, function(error) {
  console.log("Failed to save.");
  //Try again later
});
Parse.Config.get().then(function(config) {
  console.log("Yay! Config was fetched from the server.");

  const welcomeMessage = config.get("welcomeMessage");
  console.log("Welcome Message = " + welcomeMessage);
}, function(error) {
  console.log("Failed to fetch. Using Cached Config.");

  const config = Parse.Config.current();
  let welcomeMessage = config.get("welcomeMessage");
  if (welcomeMessage === undefined) {
    welcomeMessage = "Welcome!";
  }
  console.log("Welcome Message = " + welcomeMessage);
});
await Parse.Config.save(
  { welcomeMessage : "Welcome to Parse", secretMessage: "Psst 👀" },
  { secretMessage: true }
);

const publicConfig = await Parse.Config.get(); // Returns only `welcomeMessage`.
const internalConfig = await Parse.Config.get({ useMasterKey: true }); // Returns `welcomeMessage` and `secretMessage`.
// Fetches the config at most once every 12 hours per app runtime
const refreshConfig = function() {
  let lastFetchedDate;
  const configRefreshInterval = 12 * 60 * 60 * 1000;
  return function() {
    const currentDate = new Date();
    if (lastFetchedDate === undefined ||
        currentDate.getTime() - lastFetchedDate.getTime() > configRefreshInterval) {
      Parse.Config.get();
      lastFetchedDate = currentDate;
    }
  };
}();
const dimensions = {
  // Define ranges to bucket data points into meaningful segments
  priceRange: '1000-1500',
  // Did the user filter the query?
  source: 'craigslist',
  // Do searches happen more often on weekdays or weekends?
  dayType: 'weekday'
};
// Send the dimensions to Parse along with the 'search' event
Parse.Analytics.track('search', dimensions);
const codeString = '' + error.code;
Parse.Analytics.track('error', { code: codeString });
{ "results": [
  {
    "score": 1337,
    "playerName": "Sean Plott",
    "cheatMode": false,
    "createdAt": "2012-07-11T20:56:12.347Z",
    "updatedAt": "2012-07-11T20:56:12.347Z",
    "objectId": "fchpZwSuGG"
  }]
}
{ "results":
  [{
    "username": "cooldude",
    "createdAt": "1983-09-13T22:42:30.548Z",
    "updatedAt": "2015-09-04T10:12:42.137Z",
    "objectId": "ttttSEpfXm",
    "sessionToken": "dfwfq3dh0zwe5y2sqv514p4ib",
    "bcryptPassword": "$2a$10$ICV5UeEf3lICfnE9W9pN9.O9Ved/ozNo7G83Qbdk5rmyvY8l16MIK"
  }]
}
var game = new Parse.Object("Game");
game.set("createdBy", Parse.User.current());
var query = new Parse.Query("Game");
query.equalTo("createdBy", Parse.User.current());
// say we have a Game object
var game = ...

// getting the user who created the Game
var user = game.get("createdBy");
// let's say we have four weapons
var scimitar = ...
var plasmaRifle = ...
var grenade = ...
var bunnyRabbit = ...

// stick the objects in an array
var weapons = [scimitar, plasmaRifle, grenade, bunnyRabbit];

// store the weapons for the user
var user = Parse.User.current();
user.set("weaponsList", weapons);
var weapons = Parse.User.current().get("weaponsList")
// set up our query for a User object
var userQuery = new Parse.Query(Parse.User);

// configure any constraints on your query...
// for example, you may want users who are also playing with or against you

// tell the query to fetch all of the Weapon objects along with the user
// get the "many" at the same time that you're getting the "one"
userQuery.include("weaponsList");

// execute the query
// results contains all of the User objects, and their associated Weapon objects, too
const results = await userQuery.find();
// add a constraint to query for whenever a specific Weapon is in an array
userQuery.equalTo("weaponsList", scimitar);

// or query using an array of Weapon objects...
userQuery.containedIn("weaponsList", arrayOfWeapons);
// let’s say we have a few objects representing Author objects
var authorOne = ...
var authorTwo = ...
var authorThree = ...

// now we create a book object
var book = new Parse.Object("Book");

// now let’s associate the authors with the book
// remember, we created a "authors" relation on Book
var relation = book.relation("authors");
relation.add(authorOne);
relation.add(authorTwo);
relation.add(authorThree);

// now save the book object
book.save();
// suppose we have a book object
var book = ...

// create a relation based on the authors key
var relation = book.relation("authors");

// generate a query based on that relation
var query = relation.query();

// now execute the query
// suppose we have a author object, for which we want to get all books
var author = ...

// first we will create a query on the Book object
var query = new Parse.Query("Book");

// now we will query the authors relation to see if the author object we have
// is contained therein
query.equalTo("authors", author);
var otherUser = ...

// create an entry in the Follow table
var follow = new Parse.Object("Follow");
follow.set("from", Parse.User.current());
follow.set("to", otherUser);
follow.set("date", Date());
follow.save();
const query = new Parse.Query("Follow");
query.equalTo("from", Parse.User.current());
const users = await query.find();
// create an entry in the Follow table
const query = new Parse.Query("Follow");
query.equalTo("to", Parse.User.current());
const users = query.find();
// let's say we have an author
var author = ...

// and let's also say we have an book
var book = ...

// add the author to the authors list for the book
book.add("authors", author);
// set up our query for the Book object
const bookQuery = new Parse.Query("Book");

// configure any constraints on your query...
// tell the query to fetch all of the Author objects along with the Book
bookQuery.include("authors");

// execute the query
const books = await bookQuery.find();
var authorList = book.get("authors")
// set up our query for the Book object
var bookQuery = new Parse.Query("Book");

// configure any constraints on your query...
bookQuery.equalTo("authors", author);

// tell the query to fetch all of the Author objects along with the Book
bookQuery.include("authors");

// execute the query
const books = await bookQuery.find();
{
  className: 'MyClass',
  fields: {
    // Default fields
    ACL: {type: 'ACL'},
    createdAt: {type: 'Date'},
    updatedAt: {type: 'Date'},
    objectId: {type: 'String'},
    // Custom fields
    aNumber: {type: 'Number'},
    aString: {type: 'String'},
    aBool: {type: 'Boolean'},
    aDate: {type: 'Date'},
    aObject: {type: 'Object'},
    aArray: {type: 'Array'},
    aGeoPoint: {type: 'GeoPoint'},
    aPolygon: {type: 'Polygon'},
    aFile: {type: 'File'}
  },
  classLevelPermissions: {
    find: {
      '*': true
    },
    create: {
      '*': true
    },
    get: {
      '*': true
    },
    update: {
      '*': true
    },
    addField: {
      '*': true
    },
    delete: {
      '*': true
    }
  },
  indexes: {
    indexName: { aString: 1 },
  }
}
// create an instance to manage your class
const mySchema = new Parse.Schema('MyClass');

// gets the current schema data
mySchema.get();

// returns schema for all classes
Parse.Schema.all()

// add any # of fields, without having to create any objects
mySchema
  .addString('stringField')
  .addNumber('numberField')
  .addBoolean('booleanField')
  .addDate('dateField')
  .addFile('fileField')
  .addGeoPoint('geoPointField')
  .addPolygon('polygonField')
  .addArray('arrayField')
  .addObject('objectField')
  .addPointer('pointerField', '_User')
  .addRelation('relationField', '_User');

// new types can be added as they are available
mySchema.addField('newField', 'ANewDataType')

// save/update this schema to persist your field changes
mySchema.save().then((result) => {
  // returns save new schema
});
// or
mySchema.update().then((result) => {
  // updates existing schema
});
mySchema.deleteField('stringField');
mySchema.save();
// or for an existing schema...
mySchema.update();
// To add an index, the field must exist before you create an index
mySchema.addString('stringField');
const index = {
  stringField: 1
};
mySchema.addIndex('stringFieldIndex', index);
mySchema.save().then((result) => {
  // returns schema including index stringFieldIndex and field stringField
});

// Delete an index
testSchema.deleteIndex('indexName');
mySchema.save().then((result) => {
  // returns schema without indexName index
});

// If indexes exist, you can retrieve them
mySchema.get().then((result) => {
  // result.indexes
});
// delete all objects in the schema
mySchema.purge();
const query = new Parse.Query(Note);
query.get("aBcDeFgH").then((results) => {
  // This function will *not* be called.
  alert("Everything went fine!");
}, (error) => {
  // This will be called.
  // error is an instance of Parse.Error with details about the error.
  if (error.code === Parse.Error.OBJECT_NOT_FOUND) {
    alert("Uh oh, we couldn't find the object!");
  }
});

// You can also use `.catch`

query.get("aBcDeFgH").then((results) => {
  // This function will *not* be called.
  alert("Everything went fine!");
}).catch((error) => {
  // This will be called.
  // error is an instance of Parse.Error with details about the error.
  if (error.code === Parse.Error.OBJECT_NOT_FOUND) {
    alert("Uh oh, we couldn't find the object!");
  }
});
const query = new Parse.Query(Note);
query.get("thisObjectIdDoesntExist")
.then((results) => {
  // This function will *not* be called.
  alert("Everything went fine!");
}, (error) => {
  // This will be called.
  // error is an instance of Parse.Error with details about the error.
  if (error.code === Parse.Error.OBJECT_NOT_FOUND) {
    alert("Uh oh, we couldn't find the object!");
  } else if (error.code === Parse.Error.CONNECTION_FAILED) {
    alert("Uh oh, we couldn't even connect to the Parse Cloud!");
  }
});
var user = Parse.User.current();
user.setACL(new Parse.ACL(user));
// not available in the JavaScript SDK
var privateData = Parse.Object.extend("PrivateUserData");
privateData.setACL(new Parse.ACL(Parse.User.current()));
privateData.set("phoneNumber", "555-5309");

Parse.User.current().set("privateData", privateData);
var acl = new Parse.ACL();
acl.setPublicReadAccess(true);
acl.setWriteAccess(Parse.User.current().id, true);
var acl = new Parse.ACL();
acl.setPublicReadAccess(true);
acl.setRoleWriteAccess("admins", true);
{
    "*": { "read":true },
    "aSaMpLeUsErId": { "read" :true, "write": true }
}
{
    "role:RoleName": { "read": true },
    "aSaMpLeUsErId": { "read": true, "write": true }
}
{
    "<SENDER_USER_ID>": {
        "read": true,
        "write": true
    },
    "<RECEIVER_USER_ID>": {
        "read": true
    }
}
// POST http://my-parse-server.com/schemas/Announcement
// Set the X-Parse-Application-Id and X-Parse-Master-Key header
// body:
{
  classLevelPermissions:
  {
    "find": {
      "requiresAuthentication": true,
      "role:admin": true
    },
    "get": {
      "requiresAuthentication": true,
      "role:admin": true
    },
    "create": { "role:admin": true },
    "update": { "role:admin": true },
    "delete": { "role:admin": true }
  }
}
Parse.Cloud.beforeSave(Parse.User, request => {
  const user = request.object;
  if (!user.get("email")) {
    throw "Every user must have an email address.";
  }
});
Parse.Cloud.define("like", async request => {
  var post = new Parse.Object("Post");
  post.id = request.params.postId;
  post.increment("likes");
  await post.save(null, { useMasterKey: true })
});
var GameScore = Parse.Object.extend("GameScore");
var query = new Parse.Query(GameScore);
query.equalTo("score", 50);
query.containedIn("playerName",
    ["Jonathan Walsh", "Dario Wunsch", "Shawn Simon"]);
query.equalTo("cheatMode", false);
var GameScore = Parse.Object.extend("GameScore");
var query = new Parse.Query(GameScore);
query.notEqualTo("playerName", "Michael Yabuti");
query.find().then(function(results) {
  // Retrieved scores successfully
});
var query = new Parse.Query(Parse.User);
query.notEqualTo("state", "Invited");
query.containedIn("state", ["SignedUp", "Verified"]);
var GameScore = Parse.Object.extend("GameScore");
var query = new Parse.Query(GameScore);
// Previously retrieved highScore for Michael Yabuti
query.greaterThan("score", highScore);
query.find().then(function(results) {
  // Retrieved scores successfully
});
var query = new Parse.Query(Parse.User);
query.notContainedIn("state", ["Invited", "Blocked"]);
query.containedIn("state", ["SignedUp", "Verified"]);
query.matches("playerName", "Michael", "i");
query.contains("playerName", "Michael");
query.startsWith("playerName", "Michael");
query.matches("playerName", "^Michael");
query.limit(10); // limit to at most 10 results
var query = new Parse.Query(PlaceObject);
query.withinMiles("location", userGeoPoint, 10.0);
query.find().then(function(placesObjects) {
  // Get a list of objects within 10 miles of a user's location
});
var GameScore = Parse.Object.extend("GameScore");
var query = new Parse.Query(GameScore);
query.select("score", "playerName");
query.find().then(function(results) {
  // each of results will only have the selected fields available.
});
Parse.Cloud.define("averageStars", async (request) => {
  const query = new Parse.Query("Review");
  query.equalTo("movie", request.params.movie);
  const results = await query.find();
  let sum = 0;
  for (let i = 0; i < results.length; ++i) {
    sum += results[i].get("stars");
  }
  return sum / results.length;
});
Parse.Cloud.run("averageStars", { "movie": "The Matrix" }).then(function(ratings) {
  // ratings is 4.5
});
var Review = Parse.Object.extend("Review");
var query = new Parse.Query("Review");
query.equalTo("movie", movie);
query.count().then(function(count) {
  // Request succeeded
});
Parse.Cloud.afterSave("Review", function(request) {
  // Get the movie id for the Review
  var movieId = request.object.get("movie").id;
  // Query the Movie represented by this review
  var Movie = Parse.Object.extend("Movie");
  var query = new Parse.Query(Movie);
  query.get(movieId).then(function(movie) {
    // Increment the reviews field on the Movie object
    movie.increment("reviews");
    movie.save();
  }, function(error) {
    throw "Got an error " + error.code + " : " + error.message;
  });
});
var Movie = Parse.Object.extend("Movie");
var query = new Parse.Query(Movie);
query.find().then(function(results) {
  // Results include the reviews count field
}, function(error) {
  // Request failed
});
var _ = require("underscore");
Parse.Cloud.beforeSave("Post", request => {
  var post = request.object;
  var toLowerCase = function(w) { return w.toLowerCase(); };
  var words = post.get("text").split(/b/);
  words = _.map(words, toLowerCase);
  var stopWords = ["the", "in", "and"]
  words = _.filter(words, function(w) {
    return w.match(/^w+$/) && !   _.contains(stopWords, w);
  });
  var hashtags = post.get("text").match(/#.+?b/g);
  hashtags = _.map(hashtags, toLowerCase);
  post.set("words", words);
  post.set("hashtags", hashtags);
});
var Post = Parse.Object.extend("Post");
var query = new Parse.Query(Post);
query.containsAll("hashtags", ["#parse", "#ftw"]);
query.find().then(function(results) {
  // Request succeeded
}, function(error) {
  // Request failed
});

Next Lesson PHP Tutorial

Ошибки — это хорошо. Автор материала, перевод которого мы сегодня публикуем, говорит, что уверен в том, что эта идея известна всем. На первый взгляд ошибки кажутся чем-то страшным. Им могут сопутствовать какие-то потери. Ошибка, сделанная на публике, вредит авторитету того, кто её совершил. Но, совершая ошибки, мы на них учимся, а значит, попадая в следующий раз в ситуацию, в которой раньше вели себя неправильно, делаем всё как нужно.

Выше мы говорили об ошибках, которые люди совершают в обычной жизни. Ошибки в программировании — это нечто иное. Сообщения об ошибках помогают нам улучшать код, они позволяют сообщать пользователям наших проектов о том, что что-то пошло не так, и, возможно, рассказывают пользователям о том, как нужно вести себя для того, чтобы ошибок больше не возникало.

Этот материал, посвящённый обработке ошибок в JavaScript, разбит на три части. Сначала мы сделаем общий обзор системы обработки ошибок в JavaScript и поговорим об объектах ошибок. После этого мы поищем ответ на вопрос о том, что делать с ошибками, возникающими в серверном коде (в частности, при использовании связки Node.js + Express.js). Далее — обсудим обработку ошибок в React.js. Фреймворки, которые будут здесь рассматриваться, выбраны по причине их огромной популярности. Однако рассматриваемые здесь принципы работы с ошибками универсальны, поэтому вы, даже если не пользуетесь Express и React, без труда сможете применить то, что узнали, к тем инструментам, с которыми работаете.

Код демонстрационного проекта, используемого в данном материале, можно найти в этом репозитории.

1. Ошибки в JavaScript и универсальные способы работы с ними

Если в вашем коде что-то пошло не так, вы можете воспользоваться следующей конструкцией.

throw new Error('something went wrong')

В ходе выполнения этой команды будет создан экземпляр объекта Error и будет сгенерировано (или, как говорят, «выброшено») исключение с этим объектом. Инструкция throw может генерировать исключения, содержащие произвольные выражения. При этом выполнение скрипта остановится в том случае, если не были предприняты меры по обработке ошибки.

Начинающие JS-программисты обычно не используют инструкцию throw. Они, как правило, сталкиваются с исключениями, выдаваемыми либо средой выполнения языка, либо сторонними библиотеками. Когда это происходит — в консоль попадает нечто вроде ReferenceError: fs is not defined и выполнение программы останавливается.

▍Объект Error

У экземпляров объекта Error есть несколько свойств, которыми мы можем пользоваться. Первое интересующее нас свойство — message. Именно сюда попадает та строка, которую можно передать конструктору ошибки в качестве аргумента. Например, ниже показано создание экземпляра объекта Error и вывод в консоль переданной конструктором строки через обращение к его свойству message.

const myError = new Error('please improve your code')
console.log(myError.message) // please improve your code

Второе свойство объекта, очень важное, представляет собой трассировку стека ошибки. Это — свойство stack. Обратившись к нему можно просмотреть стек вызовов (историю ошибки), который показывает последовательность операций, приведшую к неправильной работе программы. В частности, это позволяет понять — в каком именно файле содержится сбойный код, и увидеть, какая последовательность вызовов функций привела к ошибке. Вот пример того, что можно увидеть, обратившись к свойству stack.

Error: please improve your code
 at Object.<anonymous> (/Users/gisderdube/Documents/_projects/hacking.nosync/error-handling/src/general.js:1:79)
 at Module._compile (internal/modules/cjs/loader.js:689:30)
 at Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10)
 at Module.load (internal/modules/cjs/loader.js:599:32)
 at tryModuleLoad (internal/modules/cjs/loader.js:538:12)
 at Function.Module._load (internal/modules/cjs/loader.js:530:3)
 at Function.Module.runMain (internal/modules/cjs/loader.js:742:12)
 at startup (internal/bootstrap/node.js:266:19)
 at bootstrapNodeJSCore (internal/bootstrap/node.js:596:3)

Здесь, в верхней части, находится сообщение об ошибке, затем следует указание на тот участок кода, выполнение которого вызвало ошибку, потом описывается то место, откуда был вызван этот сбойный участок. Это продолжается до самого «дальнего» по отношению к ошибке фрагмента кода.

▍Генерирование и обработка ошибок

Создание экземпляра объекта Error, то есть, выполнение команды вида new Error(), ни к каким особым последствиям не приводит. Интересные вещи начинают происходить после применения оператора throw, который генерирует ошибку. Как уже было сказано, если такую ошибку не обработать, выполнение скрипта остановится. При этом нет никакой разницы — был ли оператор throw использован самим программистом, произошла ли ошибка в некоей библиотеке или в среде выполнения языка (в браузере или в Node.js). Поговорим о различных сценариях обработки ошибок.

▍Конструкция try…catch

Блок try...catch представляет собой самый простой способ обработки ошибок, о котором часто забывают. В наши дни, правда, он используется гораздо интенсивнее чем раньше, благодаря тому, что его можно применять для обработки ошибок в конструкциях async/await.

Этот блок можно использовать для обработки любых ошибок, происходящих в синхронном коде. Рассмотрим пример.

const a = 5

try {
    console.log(b) // переменная b не объявлена - возникает ошибка
} catch (err) {
    console.error(err) // в консоль попадает сообщение об ошибке и стек ошибки
}

console.log(a) // выполнение скрипта не останавливается, данная команда выполняется

Если бы в этом примере мы не заключили бы сбойную команду console.log(b) в блок try...catch, то выполнение скрипта было бы остановлено.

▍Блок finally

Иногда случается так, что некий код нужно выполнить независимо от того, произошла ошибка или нет. Для этого можно, в конструкции try...catch, использовать третий, необязательный, блок — finally. Часто его использование эквивалентно некоему коду, который идёт сразу после try...catch, но в некоторых ситуациях он может пригодиться. Вот пример его использования.

const a = 5

try {
    console.log(b) // переменная b не объявлена - возникает ошибка
} catch (err) {
    console.error(err) // в консоль попадает сообщение об ошибке и стек ошибки
} finally {
    console.log(a) // этот код будет выполнен в любом случае
}

▍Асинхронные механизмы — коллбэки

Программируя на JavaScript всегда стоит обращать внимание на участки кода, выполняющиеся асинхронно. Если у вас имеется асинхронная функция и в ней возникает ошибка, скрипт продолжит выполняться. Когда асинхронные механизмы в JS реализуются с использованием коллбэков (кстати, делать так не рекомендуется), соответствующий коллбэк (функция обратного вызова) обычно получает два параметра. Это нечто вроде параметра err, который может содержать ошибку, и result — с результатами выполнения асинхронной операции. Выглядит это примерно так:

myAsyncFunc(someInput, (err, result) => {
    if(err) return console.error(err) // порядок работы с объектом ошибки мы рассмотрим позже
    console.log(result)
})

Если в коллбэк попадает ошибка, она видна там в виде параметра err. В противном случае в этот параметр попадёт значение undefined или null. Если оказалось, что в err что-то есть, важно отреагировать на это, либо так как в нашем примере, воспользовавшись командой return, либо воспользовавшись конструкцией if...else и поместив в блок else команды для работы с результатом выполнения асинхронной операции. Речь идёт о том, чтобы, в том случае, если произошла ошибка, исключить возможность работы с результатом, параметром result, который в таком случае может иметь значение undefined. Работа с таким значением, если предполагается, например, что оно содержит объект, сама может вызвать ошибку. Скажем, это произойдёт при попытке использовать конструкцию result.data или подобную ей.

▍Асинхронные механизмы — промисы

Для выполнения асинхронных операций в JavaScript лучше использовать не коллбэки а промисы. Тут, в дополнение к улучшенной читабельности кода, имеются и более совершенные механизмы обработки ошибок. А именно, возиться с объектом ошибки, который может попасть в функцию обратного вызова, при использовании промисов не нужно. Здесь для этой цели предусмотрен специальный блок catch. Он перехватывает все ошибки, произошедшие в промисах, которые находятся до него, или все ошибки, которые произошли в коде после предыдущего блока catch. Обратите внимание на то, что если в промисе произошла ошибка, для обработки которой нет блока catch, это не остановит выполнение скрипта, но сообщение об ошибке будет не особенно удобочитаемым.

(node:7741) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: something went wrong
(node:7741) DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code. */

В результате можно порекомендовать всегда, при работе с промисами, использовать блок catch. Взглянем на пример.

Promise.resolve(1)
    .then(res => {
        console.log(res) // 1

        throw new Error('something went wrong')

        return Promise.resolve(2)
    })
    .then(res => {
        console.log(res) // этот блок выполнен не будет
    })
    .catch(err => {
        console.error(err) // о том, что делать с этой ошибкой, поговорим позже
        return Promise.resolve(3)
    })
    .then(res => {
        console.log(res) // 3
    })
    .catch(err => {
        // этот блок тут на тот случай, если в предыдущем блоке возникнет какая-нибудь ошибка
        console.error(err)
    })

▍Асинхронные механизмы и try…catch

После того, как в JavaScript появилась конструкция async/await, мы вернулись к классическому способу обработки ошибок — к try...catch...finally. Обрабатывать ошибки при таком подходе оказывается очень легко и удобно. Рассмотрим пример.

;(async function() {
    try {
        await someFuncThatThrowsAnError()
    } catch (err) {
        console.error(err) // об этом поговорим позже
    }

    console.log('Easy!') // будет выполнено
})()

При таком подходе ошибки в асинхронном коде обрабатываются так же, как в синхронном. В результате теперь, при необходимости, в одном блоке catch можно обрабатывать более широкий диапазон ошибок.

2. Генерирование и обработка ошибок в серверном коде

Теперь, когда у нас есть инструменты для работы с ошибками, посмотрим на то, что мы можем с ними делать в реальных ситуациях. Генерирование и правильная обработка ошибок — это важнейший аспект серверного программирования. Существуют разные подходы к работе с ошибками. Здесь будет продемонстрирован подход с использованием собственного конструктора для экземпляров объекта Error и кодов ошибок, которые удобно передавать во фронтенд или любым механизмам, использующим серверные API. Как структурирован бэкенд конкретного проекта — особого значения не имеет, так как при любом подходе можно использовать одни и те же идеи, касающиеся работы с ошибками.

В качестве серверного фреймворка, отвечающего за маршрутизацию, мы будем использовать Express.js. Подумаем о том, какая структура нам нужна для организации эффективной системы обработки ошибок. Итак, вот что нам нужно:

  1. Универсальная обработка ошибок — некий базовый механизм, подходящий для обработки любых ошибок, в ходе работы которого просто выдаётся сообщение наподобие Something went wrong, please try again or contact us, предлагающее пользователю попробовать выполнить операцию, давшую сбой, ещё раз или связаться с владельцем сервера. Эта система не отличается особой интеллектуальностью, но она, по крайней мере, способна сообщить пользователю о том, что что-то пошло не так. Подобное сообщение гораздо лучше, чем «бесконечная загрузка» или нечто подобное.
  2. Обработка конкретных ошибок — механизм, позволяющий сообщить пользователю подробные сведения о причинах неправильного поведения системы и дать ему конкретные советы по борьбе с неполадкой. Например, это может касаться отсутствия неких важных данных в запросе, который пользователь отправляет на сервер, или в том, что в базе данных уже существует некая запись, которую он пытается добавить ещё раз, и так далее.

▍Разработка собственного конструктора объектов ошибок

Здесь мы воспользуемся стандартным классом Error и расширим его. Пользоваться механизмами наследования в JavaScript — дело рискованное, но в данном случае эти механизмы оказываются весьма полезными. Зачем нам наследование? Дело в том, что нам, для того, чтобы код удобно было бы отлаживать, нужны сведения о трассировке стека ошибки. Расширяя стандартный класс Error, мы, без дополнительных усилий, получаем возможности по трассировке стека. Мы добавляем в наш собственный объект ошибки два свойства. Первое — это свойство code, доступ к которому можно будет получить с помощью конструкции вида err.code. Второе — свойство status. В него будет записываться код состояния HTTP, который планируется передавать клиентской части приложения.

Вот как выглядит класс CustomError, код которого оформлен в виде модуля.

class CustomError extends Error {
    constructor(code = 'GENERIC', status = 500, ...params) {
        super(...params)

        if (Error.captureStackTrace) {
            Error.captureStackTrace(this, CustomError)
        }

        this.code = code
        this.status = status
    }
}

module.exports = CustomError

▍Маршрутизация

Теперь, когда наш объект ошибки готов к использованию, нужно настроить структуру маршрутов. Как было сказано выше, нам требуется реализовать унифицированный подход к обработке ошибок, позволяющий одинаково обрабатывать ошибки для всех маршрутов. По умолчанию фреймворк Express.js не вполне поддерживает такую схему работы. Дело в том, что все его маршруты инкапсулированы.

Для того чтобы справиться с этой проблемой, мы можем реализовать собственный обработчик маршрутов и определять логику маршрутов в виде обычных функций. Благодаря такому подходу, если функция маршрута (или любая другая функция) выбрасывает ошибку, она попадёт в обработчик маршрутов, который затем может передать её клиентской части приложения. При возникновении ошибки на сервере мы планируем передавать её во фронтенд в следующем формате, полагая, что для этого будет применяться JSON-API:

{
    error: 'SOME_ERROR_CODE',
    description: 'Something bad happened. Please try again or contact support.'
}

Если на данном этапе происходящие кажется вам непонятным — не беспокойтесь — просто продолжайте читать, пробуйте работать с тем, о чём идёт речь, и постепенно вы во всём разберётесь. На самом деле, если говорить о компьютерном обучении, здесь применяется подход «сверху-вниз», когда сначала обсуждаются общие идеи, а потом осуществляется переход к частностям.

Вот как выглядит код обработчика маршрутов.

const express = require('express')
const router = express.Router()
const CustomError = require('../CustomError')

router.use(async (req, res) => {
    try {
        const route = require(`.${req.path}`)[req.method]

        try {
            const result = route(req) // Передаём запрос функции route
            res.send(result) // Передаём клиенту то, что получено от функции route
        } catch (err) {
            /*
            Сюда мы попадаем в том случае, если в функции route произойдёт ошибка
            */
            if (err instanceof CustomError) {
                /* 
                Если ошибка уже обработана - трансформируем её в 
                возвращаемый объект
                */

                return res.status(err.status).send({
                    error: err.code,
                    description: err.message,
                })
            } else {
                console.error(err) // Для отладочных целей

                // Общая ошибка - вернём универсальный объект ошибки
                return res.status(500).send({
                    error: 'GENERIC',
                    description: 'Something went wrong. Please try again or contact support.',
                })
            }
        }
    } catch (err) {
        /* 
         Сюда мы попадём, если запрос окажется неудачным, то есть,
         либо не будет найдено файла, соответствующего пути, переданному
         в запросе, либо не будет экспортированной функции с заданным
         методом запроса
        */
        res.status(404).send({
            error: 'NOT_FOUND',
            description: 'The resource you tried to access does not exist.',
        })
    }
})

module.exports = router

Полагаем, комментарии в коде достаточно хорошо его поясняют. Надеемся, читать их удобнее, чем объяснения подобного кода, данные после него.

Теперь взглянем на файл маршрутов.

const CustomError = require('../CustomError')

const GET = req => {
    // пример успешного выполнения запроса
    return { name: 'Rio de Janeiro' }
}

const POST = req => {
    // пример ошибки общего характера
    throw new Error('Some unexpected error, may also be thrown by a library or the runtime.')
}

const DELETE = req => {
    // пример ошибки, обрабатываемой особым образом
    throw new CustomError('CITY_NOT_FOUND', 404, 'The city you are trying to delete could not be found.')
}

const PATCH = req => {
    // пример перехвата ошибок и использования CustomError
    try {
        // тут случилось что-то нехорошее
        throw new Error('Some internal error')
    } catch (err) {
        console.error(err) // принимаем решение о том, что нам тут делать

        throw new CustomError(
            'CITY_NOT_EDITABLE',
            400,
            'The city you are trying to edit is not editable.'
        )
    }
}

module.exports = {
    GET,
    POST,
    DELETE,
    PATCH,
}

В этих примерах с самими запросами ничего не делается. Тут просто рассматриваются разные сценарии возникновения ошибок. Итак, например, запрос GET /city попадёт в функцию const GET = req =>..., запрос POST /city попадёт в функцию const POST = req =>... и так далее. Эта схема работает и при использовании параметров запросов. Например — для запроса вида GET /city?startsWith=R. В целом, здесь продемонстрировано, что при обработке ошибок, во фронтенд может попасть либо общая ошибка, содержащая лишь предложение попробовать снова или связаться с владельцем сервера, либо ошибка, сформированная с использованием конструктора CustomError, которая содержит подробные сведения о проблеме.
Данные общей ошибки придут в клиентскую часть приложения в таком виде:

{
    error: 'GENERIC',
    description: 'Something went wrong. Please try again or contact support.'
}

Конструктор CustomError используется так:

throw new CustomError('MY_CODE', 400, 'Error description')

Это даёт следующий JSON-код, передаваемый во фронтенд:

{
    error: 'MY_CODE',
    description: 'Error description'
}

Теперь, когда мы основательно потрудились над серверной частью приложения, в клиентскую часть больше не попадают бесполезные логи ошибок. Вместо этого клиент получает полезные сведения о том, что пошло не так.

Не забудьте о том, что здесь лежит репозиторий с рассматриваемым здесь кодом. Можете его загрузить, поэкспериментировать с ним, и, если надо, адаптировать под нужды вашего проекта.

3. Работа с ошибками на клиенте

Теперь пришла пора описать третью часть нашей системы обработки ошибок, касающуюся фронтенда. Тут нужно будет, во-первых, обрабатывать ошибки, возникающие в клиентской части приложения, а во-вторых, понадобится оповещать пользователя об ошибках, возникающих на сервере. Разберёмся сначала с показом сведений о серверных ошибках. Как уже было сказано, в этом примере будет использована библиотека React.

▍Сохранение сведений об ошибках в состоянии приложения

Как и любые другие данные, ошибки и сообщения об ошибках могут меняться, поэтому их имеет смысл помещать в состояние компонентов. При монтировании компонента данные об ошибке сбрасываются, поэтому, когда пользователь впервые видит страницу, там сообщений об ошибках не будет.

Следующее, с чем надо разобраться, заключается в том, что ошибки одного типа нужно показывать в одном стиле. По аналогии с сервером, здесь можно выделить 3 типа ошибок.

  1. Глобальные ошибки — в эту категорию попадают сообщения об ошибках общего характера, приходящие с сервера, или ошибки, которые, например, возникают в том случае, если пользователь не вошёл в систему и в других подобных ситуациях.
  2. Специфические ошибки, выдаваемые серверной частью приложения — сюда относятся ошибки, сведения о которых приходят с сервера. Например, подобная ошибка возникает, если пользователь попытался войти в систему и отправил на сервер имя и пароль, а сервер сообщил ему о том, что пароль неправильный. Подобные вещи в клиентской части приложения не проверяются, поэтому сообщения о таких ошибках должны приходить с сервера.
  3. Специфические ошибки, выдаваемые клиентской частью приложения. Пример такой ошибки — сообщение о некорректном адресе электронной почты, введённом в соответствующее поле.

Ошибки второго и третьего типов очень похожи, работать с ними можно, используя хранилище состояния компонентов одного уровня. Их главное различие заключается в том, что они исходят из разных источников. Ниже, анализируя код, мы посмотрим на работу с ними.

Здесь будет использоваться встроенная в React система управления состоянием приложения, но, при необходимости, вы можете воспользоваться и специализированными решениями для управления состоянием — такими, как MobX или Redux.

▍Глобальные ошибки

Обычно сообщения о таких ошибках сохраняются в компоненте наиболее высокого уровня, имеющем состояние. Они выводятся в статическом элементе пользовательского интерфейса. Это может быть красное поле в верхней части экрана, модальное окно или что угодно другое. Реализация зависит от конкретного проекта. Вот как выглядит сообщение о такой ошибке.

Сообщение о глобальной ошибке

Теперь взглянем на код, который хранится в файле Application.js.

import React, { Component } from 'react'

import GlobalError from './GlobalError'

class Application extends Component {
    constructor(props) {
        super(props)

        this.state = {
            error: '',
        }

        this._resetError = this._resetError.bind(this)
        this._setError = this._setError.bind(this)
    }

    render() {
        return (
            <div className="container">
                <GlobalError error={this.state.error} resetError={this._resetError} />
                <h1>Handling Errors</h1>
            </div>
        )
    }

    _resetError() {
        this.setState({ error: '' })
    }

    _setError(newError) {
        this.setState({ error: newError })
    }
}

export default Application

Как видно, в состоянии, в Application.js, имеется место для хранения данных ошибки. Кроме того, тут предусмотрены методы для сброса этих данных и для их изменения.

Ошибка и метод для сброса ошибки передаётся компоненту GlobalError, который отвечает за вывод сообщения об ошибке на экран и за сброс ошибки после нажатия на значок x в поле, где выводится сообщение. Вот код компонента GlobalError (файл GlobalError.js).

import React, { Component } from 'react'

class GlobalError extends Component {
    render() {
        if (!this.props.error) return null

        return (
            <div
                style={{
                    position: 'fixed',
                    top: 0,
                    left: '50%',
                    transform: 'translateX(-50%)',
                    padding: 10,
                    backgroundColor: '#ffcccc',
                    boxShadow: '0 3px 25px -10px rgba(0,0,0,0.5)',
                    display: 'flex',
                    alignItems: 'center',
                }}
            >
                {this.props.error}
                 
                <i
                    className="material-icons"
                    style={{ cursor: 'pointer' }}
                    onClick={this.props.resetError}
                >
                    close
                </font></i>
            </div>
        )
    }
}

export default GlobalError

Обратите внимание на строку if (!this.props.error) return null. Она указывает на то, что при отсутствии ошибки компонент ничего не выводит. Это предотвращает постоянный показ красного прямоугольника на странице. Конечно, вы, при желании, можете поменять внешний вид и поведение этого компонента. Например, вместо того, чтобы сбрасывать ошибку по нажатию на x, можно задать тайм-аут в пару секунд, по истечении которого состояние ошибки сбрасывается автоматически.

Теперь, когда всё готово для работы с глобальными ошибками, для задания глобальной ошибки достаточно воспользоваться _setError из Application.js. Например, это можно сделать в том случае, если сервер, после обращения к нему, вернул сообщение об общей ошибке (error: 'GENERIC'). Рассмотрим пример (файл GenericErrorReq.js).

import React, { Component } from 'react'
import axios from 'axios'

class GenericErrorReq extends Component {
    constructor(props) {
        super(props)

        this._callBackend = this._callBackend.bind(this)
    }

    render() {
        return (
            <div>
                <button onClick={this._callBackend}>Click me to call the backend</button>
            </div>
        )
    }

    _callBackend() {
        axios
            .post('/api/city')
            .then(result => {
                // сделать что-нибудь с результатом в том случае, если запрос оказался успешным
            })
            .catch(err => {
                if (err.response.data.error === 'GENERIC') {
                    this.props.setError(err.response.data.description)
                }
            })
    }
}

export default GenericErrorReq

На самом деле, на этом наш разговор об обработке ошибок можно было бы и закончить. Даже если в проекте нужно оповещать пользователя о специфических ошибках, никто не мешает просто поменять глобальное состояние, хранящее ошибку и вывести соответствующее сообщение поверх страницы. Однако тут мы не остановимся и поговорим о специфических ошибках. Во-первых, это руководство по обработке ошибок иначе было бы неполным, а во-вторых, с точки зрения UX-специалистов, неправильно будет показывать сообщения обо всех ошибках так, будто все они — глобальные.

▍Обработка специфических ошибок, возникающих при выполнении запросов

Вот пример специфического сообщения об ошибке, выводимого в том случае, если пользователь пытается удалить из базы данных город, которого там нет.

Сообщение о специфической ошибке

Тут используется тот же принцип, который мы применяли при работе с глобальными ошибками. Только сведения о таких ошибках хранятся в локальном состоянии соответствующих компонентов. Работа с ними очень похожа на работу с глобальными ошибками. Вот код файла SpecificErrorReq.js.

import React, { Component } from 'react'
import axios from 'axios'

import InlineError from './InlineError'

class SpecificErrorRequest extends Component {
    constructor(props) {
        super(props)

        this.state = {
            error: '',
        }

        this._callBackend = this._callBackend.bind(this)
    }

    render() {
        return (
            <div>
                <button onClick={this._callBackend}>Delete your city</button>
                <InlineError error={this.state.error} />
            </div>
        )
    }

    _callBackend() {
        this.setState({
            error: '',
        })

        axios
            .delete('/api/city')
            .then(result => {
                // сделать что-нибудь с результатом в том случае, если запрос оказался успешным
            })
            .catch(err => {
                if (err.response.data.error === 'GENERIC') {
                    this.props.setError(err.response.data.description)
                } else {
                    this.setState({
                        error: err.response.data.description,
                    })
                }
            })
    }
}

export default SpecificErrorRequest

Тут стоит отметить, что для сброса специфических ошибок недостаточно, например, просто нажать на некую кнопку x. То, что пользователь прочёл сообщение об ошибке и закрыл его, не помогает такую ошибку исправить. Исправить её можно, правильно сформировав запрос к серверу, например — введя в ситуации, показанной на предыдущем рисунке, имя города, который есть в базе. В результате очищать сообщение об ошибке имеет смысл, например, после выполнения нового запроса. Сбросить ошибку можно и в том случае, если пользователь внёс изменения в то, что будет использоваться при формировании нового запроса, то есть — при изменении содержимого поля ввода.

▍Ошибки, возникающие в клиентской части приложения

Как уже было сказано, для хранения данных о таких ошибках можно использовать состояние тех же компонентов, которое используется для хранения данных по специфическим ошибкам, поступающим с сервера. Предположим, мы позволяем пользователю отправить на сервер запрос на удаление города из базы только в том случае, если в соответствующем поле ввода есть какой-то текст. Отсутствие или наличие текста в поле можно проверить средствами клиентской части приложения.

В поле ничего нет, мы сообщаем об этом пользователю

Вот код файла SpecificErrorFrontend.js, реализующий вышеописанный функционал.

import React, { Component } from 'react'
import axios from 'axios'

import InlineError from './InlineError'

class SpecificErrorRequest extends Component {
    constructor(props) {
        super(props)

        this.state = {
            error: '',
            city: '',
        }

        this._callBackend = this._callBackend.bind(this)
        this._changeCity = this._changeCity.bind(this)
    }

    render() {
        return (
            <div>
                <input
                    type="text"
                    value={this.state.city}
                    style={{ marginRight: 15 }}
                    onChange={this._changeCity}
                />
                <button onClick={this._callBackend}>Delete your city</button>
                <InlineError error={this.state.error} />
            </div>
        )
    }

    _changeCity(e) {
        this.setState({
            error: '',
            city: e.target.value,
        })
    }

    _validate() {
        if (!this.state.city.length) throw new Error('Please provide a city name.')
    }

    _callBackend() {
        this.setState({
            error: '',
        })

        try {
            this._validate()
        } catch (err) {
            return this.setState({ error: err.message })
        }

        axios
            .delete('/api/city')
            .then(result => {
                // сделать что-нибудь с результатом в том случае, если запрос оказался успешным
            })
            .catch(err => {
                if (err.response.data.error === 'GENERIC') {
                    this.props.setError(err.response.data.description)
                } else {
                    this.setState({
                        error: err.response.data.description,
                    })
                }
            })
    }
}

export default SpecificErrorRequest

▍Интернационализация сообщений об ошибках с использованием кодов ошибок

Возможно, сейчас вы задаётесь вопросом о том, зачем нам нужны коды ошибок (наподобие GENERIC), если мы показываем пользователю только сообщения об ошибках, полученных с сервера. Дело в том, что, по мере роста и развития приложения, оно, вполне возможно, выйдет на мировой рынок, а это означает, что настанет время, когда создателям приложения нужно будет задуматься о поддержке им нескольких языков. Коды ошибок позволяют отличать их друг от друга и выводить сообщения о них на языке пользователя сайта.

Итоги

Надеемся, теперь у вас сформировалось понимание того, как можно работать с ошибками в веб-приложениях. Нечто вроде console.error(err) следует использовать только в отладочных целях, в продакшн подобные вещи, забытые программистом, проникать не должны. Упрощает решение задачи логирования использование какой-нибудь подходящей библиотеки наподобие loglevel.

Уважаемые читатели! Как вы обрабатываете ошибки в своих проектах?

SyntaxError: JSON.parse: bad parsing Breaking Your Code? Your JSON is Invalid

A refresher on the purpose and syntax of JSON, as well as a detailed exploration of the JSON Parse SyntaxError in JavaScript.

Traveling deftly through to the next item in our JavaScript Error Handling series, today we’re taking a hard look at the JSON Parse error. The JSON Parse error, as the name implies, surfaces when using the JSON.parse() method that fails to pass valid JSON as an argument.

In this article, we’ll dig deeper into where JSON Parse errors sit in the JavaScript error hierarchy, as well as when it might appear and how to handle it when it does. Let’s get started!

The Technical Rundown

  • All JavaScript error objects are descendants of the Error object, or an inherited object therein.
  • The SyntaxError object is inherited from the Error object.
  • The JSON Parse error is a specific type of SyntaxError object.

When Should You Use It?

While most developers are probably intimately familiar with JSON and the proper formatting syntax it requires, it doesn’t hurt to briefly review it ourselves, to better understand some common causes of the JSON Parse error in JavaScript.

JavaScript Object Notation, better known as JSON, is a human-readable text format, commonly used to transfer data across the web. The basic structure of JSON consists of objects, which are sets of string: value pairs surrounded by curly braces:

{
"first": "Jane",
"last": "Doe"
}

An array is a set of values, surrounded by brackets:

[
"Jane",
"Doe"
]

A value can be a string, number, object, array, boolean, or null.

That’s really all there is to the JSON syntax. Since values can be other objects or arrays, JSON can be infinitely nested (theoretically).

In JavaScript, when passing JSON to the JSON.parse() method, the method expects properly formatted JSON as the first argument. When it detects invalid JSON, it throws a JSON Parse error.

For example, one of the most common typos or syntax errors in JSON is adding an extra comma separator at the end of an array or object value set. Notice in the first few examples above, we only use a comma to literally separate values from one another. Here we’ll try adding an extra, or «hanging», comma after our final value:

var printError = function(error, explicit) {
console.log(`[${explicit ? 'EXPLICIT' : 'INEXPLICIT'}] ${error.name}: ${error.message}`);
}

try {
var json = `
{
«first»: «Jane»,
«last»: «Doe»,
}
`
console.log(JSON.parse(json));
} catch (e) {
if (e instanceof SyntaxError) {
printError(e, true);
} else {
printError(e, false);
}
}

Note: We’re using the backtick (`) string syntax to initialize our JSON, which just allows us to present it in a more readable form. Functionally, this is identical to a string that is contained on a single line.

As expected, our extraneous comma at the end throws a JSON Parse error:

[EXPLICIT] SyntaxError: Unexpected token } in JSON at position 107

In this case, it’s telling us the } token is unexpected, because the comma at the end informs JSON that there should be a third value to follow.

Another common syntax issue is neglecting to surround string values within string: value pairs with quotations ("). Many other language syntaxes use similar key: value pairings to indicate named arguments and the like, so developers may find it easy to forget that JSON requires the string to be explicitly indicated using quotation marks:

var printError = function(error, explicit) {
console.log(`[${explicit ? 'EXPLICIT' : 'INEXPLICIT'}] ${error.name}: ${error.message}`);
}

try {
var json = `
{
«first»: «Jane»,
last: «Doe»,
}
`
console.log(JSON.parse(json));
} catch (e) {
if (e instanceof SyntaxError) {
printError(e, true);
} else {
printError(e, false);
}
}

Here we forgot quotations around the "last" key string, so we get another JSON Parse error:

[EXPLICIT] SyntaxError: Unexpected token l in JSON at position 76

A few examples are probably sufficient to see how the JSON Parse error comes about, but as it so happens, there are dozens of possible versions of this error, depending on how JSON was improperly formatted. Here’s the full list:

JSON Parse Error Messages
SyntaxError: JSON.parse: unterminated string literal
SyntaxError: JSON.parse: bad control character in string literal
SyntaxError: JSON.parse: bad character in string literal
SyntaxError: JSON.parse: bad Unicode escape
SyntaxError: JSON.parse: bad escape character
SyntaxError: JSON.parse: unterminated string
SyntaxError: JSON.parse: no number after minus sign
SyntaxError: JSON.parse: unexpected non-digit
SyntaxError: JSON.parse: missing digits after decimal point
SyntaxError: JSON.parse: unterminated fractional number
SyntaxError: JSON.parse: missing digits after exponent indicator
SyntaxError: JSON.parse: missing digits after exponent sign
SyntaxError: JSON.parse: exponent part is missing a number
SyntaxError: JSON.parse: unexpected end of data
SyntaxError: JSON.parse: unexpected keyword
SyntaxError: JSON.parse: unexpected character
SyntaxError: JSON.parse: end of data while reading object contents
SyntaxError: JSON.parse: expected property name or ‘}’
SyntaxError: JSON.parse: end of data when ‘,’ or ‘]’ was expected
SyntaxError: JSON.parse: expected ‘,’ or ‘]’ after array element
SyntaxError: JSON.parse: end of data when property name was expected
SyntaxError: JSON.parse: expected double-quoted property name
SyntaxError: JSON.parse: end of data after property name when ‘:’ was expected
SyntaxError: JSON.parse: expected ‘:’ after property name in object
SyntaxError: JSON.parse: end of data after property value in object
SyntaxError: JSON.parse: expected ‘,’ or ‘}’ after property value in object
SyntaxError: JSON.parse: expected ‘,’ or ‘}’ after property-value pair in object literal
SyntaxError: JSON.parse: property names must be double-quoted strings
SyntaxError: JSON.parse: expected property name or ‘}’
SyntaxError: JSON.parse: unexpected character
SyntaxError: JSON.parse: unexpected non-whitespace character after JSON data

To dive even deeper into understanding how your applications deal with JavaScript Errors, check out the revolutionary Airbrake JavaScript error tracking tool for real-time alerts and instantaneous insight into what went wrong with your JavaScript code.

An Easier Way to Find JavaScript Errors

The first way to find a JSON Parse error is to go through your logs, but when you’re dealing with hundreds, if not thousands, of lines of code, it can be difficult to find that one line of broken code. With Airbrake Error and Performance Monitoring, you can skip the logs and go straight to the line of broken code resulting in the JSON Parse error.

Don’t have Airbrake? Create your free Airbrake dev account today!

Try Airbrake, Create a Free Dev Account

Note: We published this post in February 2017 and recently updated it in April 2022.

The JavaScript exceptions thrown by JSON.parse() occur when string failed to be parsed as JSON.

Message

SyntaxError: JSON.parse: unterminated string literal
SyntaxError: JSON.parse: bad control character in string literal
SyntaxError: JSON.parse: bad character in string literal
SyntaxError: JSON.parse: bad Unicode escape
SyntaxError: JSON.parse: bad escape character
SyntaxError: JSON.parse: unterminated string
SyntaxError: JSON.parse: no number after minus sign
SyntaxError: JSON.parse: unexpected non-digit
SyntaxError: JSON.parse: missing digits after decimal point
SyntaxError: JSON.parse: unterminated fractional number
SyntaxError: JSON.parse: missing digits after exponent indicator
SyntaxError: JSON.parse: missing digits after exponent sign
SyntaxError: JSON.parse: exponent part is missing a number
SyntaxError: JSON.parse: unexpected end of data
SyntaxError: JSON.parse: unexpected keyword
SyntaxError: JSON.parse: unexpected character
SyntaxError: JSON.parse: end of data while reading object contents
SyntaxError: JSON.parse: expected property name or 
SyntaxError: JSON.parse: end of data when 
SyntaxError: JSON.parse: expected 
SyntaxError: JSON.parse: end of data when property name was expected
SyntaxError: JSON.parse: expected double-quoted property name
SyntaxError: JSON.parse: end of data after property name when 
SyntaxError: JSON.parse: expected 
SyntaxError: JSON.parse: end of data after property value in object
SyntaxError: JSON.parse: expected 
SyntaxError: JSON.parse: expected 
SyntaxError: JSON.parse: property names must be double-quoted strings
SyntaxError: JSON.parse: expected property name or 
SyntaxError: JSON.parse: unexpected character
SyntaxError: JSON.parse: unexpected non-whitespace character after JSON data

Error type

What went wrong?

JSON.parse() parses a string as JSON. This string has to be valid JSON and will throw this error if incorrect syntax was encountered.

Examples

JSON.parse() does not allow trailing commas

Both lines will throw a SyntaxError:

JSON.parse('[1, 2, 3, 4,]');
JSON.parse('{"foo": 1,}');


Omit the trailing commas to parse the JSON correctly:

JSON.parse('[1, 2, 3, 4]');
JSON.parse('{"foo": 1}');

Property names must be double-quoted strings

You cannot use single-quotes around properties, like ‘foo’.

JSON.parse("{'foo': 1}");


Instead write «foo»:

JSON.parse('{"foo": 1}');

Leading zeros and decimal points

You cannot use leading zeros, like 01, and decimal points must be followed by at least one digit.

JSON.parse('{"foo": 01}');



JSON.parse('{"foo": 1.}');


Instead write just 1 without a zero and use at least one digit after a decimal point:

JSON.parse('{"foo": 1}');
JSON.parse('{"foo": 1.0}');

See also

  • JSON
  • JSON.parse()
  • JSON.stringify()


JavaScript

  • TypeError: invalid ‘instanceof’ operand ‘x’

    The JavaScript exception «invalid ‘instanceof’ operand» occurs when right-hand side operands operator isn’t used with constructor object, i.e.

  • TypeError: ‘x’ is not iterable

    The JavaScript exception «is not iterable» occurs when value which given right-hand side of for…of, argument function such Promise.all TypedArray.from,

  • SyntaxError: Malformed formal parameter

    The JavaScript exception «malformed formal parameter» occurs when argument list of Function() constructor call invalid somehow.

  • URIError: malformed URI sequence

    The JavaScript exception «malformed URI sequence» occurs when encoding decoding wasn’t successful.

If a JavaScript program is syntactically incorrect, the interpreter will show a relevant message and a message showing the file and line where the error might have occurred. Syntax errors occur when the code has grammatical mistakes. Grammar is essential to human language, but a text with grammar mistakes can still be read and understood. However, when it comes to programming, things are much more strict. Even a tiny mistake can mean your program won’t run. Maybe you’ve mixed up your brackets, or there’s a ; that you forgot to add — these are just some examples of such mistakes.

Here is an example of some code with a syntax error:

console.log('Hodor'

If we run this code we will see the following message: SyntaxError: missing ) after the argument list. In JavaScript, these errors are labelled as «SyntaxError». For technical reasons, this code running on code-basics won’t show a message with a line and file. You can check this output on repl.it.

On the one hand, syntax errors are the most obvious because they deal with code grammar rules and have nothing to do with its logic. You can easily fix it once you find it.

On the other hand, an interpreter will not always tell you the correct position of an error. Sometimes you need to add a forgotten bracket to different place than what the error message says.

Instructions

This task isn’t directly related to the lesson, but it would be useful to practice printing anyway.

Print the following text What Is Dead May Never Die.

The exercise doesn’t pass checking. What to do? 😶

If you’ve reached a deadlock it’s time to ask your question in the «Discussions». How ask a question correctly:

  • Be sure to attach the test output, without it it’s almost impossible to figure out what went wrong, even if you show your code. It’s complicated for developers to execute code in their heads, but having a mistake before their eyes most probably will be helpful.

In my environment the code works, but not here 🤨

Tests are designed so that they test the solution in different ways and against different data. Often the solution works with one kind of input data but doesn’t work with others. Check the «Tests» tab to figure this out, you can find hints at the error output.

My code is different from the teacher’s one 🤔

It’s fine. 🙆 One task in programming can be solved in many different ways. If your code passed all tests, it complies with the task conditions.

In some rare cases, the solution may be adjusted to the tests, but this can be seen immediately.

I’ve read the lessons but nothing is clear 🙄

It’s hard to make educational materials that will suit everyone. We do our best but there is always something to improve. If you see a material that is not clear to you, describe the problem in “Discussions”. It will be great if you’ll write unclear points in the question form. Usually, we need a few days for corrections.

By the way, you can participate in courses improvement. There is a link below to the lessons course code which you can edit right in your browser.

Tips

  • JavaScript errors

Definitions

  • Syntax errors are grammar mistakes in a programming language.

  • SyntaxError (Parse error) is a type of error in JavaScript which occurs when there are syntax errors in the code.

Continuous Test Orchestration And Execution Platform Online

Perform automated and live-interactive testing on 3000+ real desktop and mobile devices online.

Arnab Roy Chowdhury

Posted On: July 27, 2018

view count20442 Views

Read time5 Min Read

JavaScript is criticized as a language that is quite difficult to debug. It doesn’t matter how perfect the code of a front-end application is, some of its functionality will get impacted especially when we get down to test it’s compatbility across different browsers. The errors occur mostly because many times developers use modern Web API or ECMA 6 scripts in their codes that are not yet browser compatible even in some most popular browser versions. In this article, we will look at the errors commonly faced by developers in their front-end application and how to minimize or get rid of them.

Uncaught TypeError

If you are debugging your JavaScript code in Google Chrome, you may have seen this error for several times. This occurs when you call a method or read a property on an undefined object.

Uncaught TypeError

Although this error can occur due to many reasons, a common one is the state of an element not properly initialized when UI components are rendered. Not only in JavaScript, this issue can also occur in an application developed using React JS or Angular jS. Let’s see how to fix it.

Fix – The state should be initialized using a reasonable default value in constructors.

Loading and Runtime Errors

Just like Java and C++ has compile errors, JavaScript has loading errors. Developers will not be able to detect a prominent error until it is loaded in the browser. Once loaded, it can be spotted easily when an error message is displayed showing syntax error. Runtime errors occur when the interpreter comes along some code that it cannot understand. Both loading and syntax errors are simple but can result in unexpected halting of script.

Fix – Once these errors occur, debugger in the browser can be used to find out the error in code. Misspelt syntax, a semicolon that is missed while typing, generally causes this error.

Cross Browser Testing

Null Object Error in Safari

This is a major cross browser compatibility issue typically associated with Safari Browsers. Often in Safari, an error occurs, and a message is displayed in the console that null is not an object. This occurs when a method is called on a null object. One can test this easily in the developer console of Safari. If you don’t have a mac system handy, you can try it out at here LambdaTest. We have more than 3000 combinations of browsers and operating systems, including Mac and Safari browsers, where you can test your website for browser compatibility.

Null Object Error in Safari

Null and undefined are not same in JavaScript. Null means that the object has a blank value. Undefined means a variable that is not assigned. Using a strict equality operator can verify that they are not equal.

Null and undefined Object Error in Safari

This error also occurs when the developer tries to use a DOM element before it is loaded.

Fix – An event listener is the perfect solution for this type of errors. It notifies the developer once the page is ready. The init() method can use the DOM elements once the event listener is fired.

Parse Errors

Often when hard coded values are used in JavaScript, during compilation, the code throws parse error. This occurs mostly when hard line breaks exist in the code. The compiler interprets line breaks as line ending semi-colons.

Fix– Always use parenthesis and semi-colons to make your code easier to read and avoid breaking lines.

download whitepaper

(unknown): Script error

When the domain boundary is crossed by an uncaught JavaScript error, it violates the cross-origin policy and results in Script error. Uncaught errors are those which are not caught inside try-catch and bubble up on the window.onerror handler. A common example is, if your JavaScript code is hosted in CDN, any uncaught error gets reported as Script error. This is a security protocol built in browsers to prevent passing of data across domains which is not permitted otherwise.

Fix – Try-catch should always be used to handle errors

TypeError – Property Not Supported by Object

This error usually occurs in Internet Explorer when an undefined method is called. It can be compared to the undefined function error that occur in Chrome. For web applications that use JavaScript namespacing, this error is quite common. IE cannot bind “this” keyword to the current namespace. For example, this.isAwesome() works properly in all browsers but throws an exception in Chrome.

TypeError

Fix – When using namespacing, this error can be avoided by using the actual namespace as a prefix

TypeError – Cannot Read Length

This error occurs mostly in Chrome due to an undefined variable’s length property. Normally an array has its length defined. But when the variable name of an array remains hidden or if the array is not initialized, this error happens.

TypeError – Cannot Read Length

Fix – This error can be fixed in 2 ways

  • In the statement where the function is declared, parameters should be removed.
  • The function should be invoked in the array that is declared.

Most of the errors that occur when the browser compiles the JavaScript are either undefined or null type errors. If the developer uses the strict compiler in a static checking system like Typescript, these errors can be avoided. It will give the warning that a type is not defined but expected. Even if Typescript is not used, guard clauses can be used to check if the objects are undefined before they are used.

Check Browser Compatibility

Arnab Roy Chowdhury

Arnab Roy Chowdhury is a UI developer by profession and a blogging enthusiast. He has been writing content for about 5 years and has strong expertise in technical blogs, travelogues, and content in the latest programming languages.

Author Profile
Author Profile
Author Profile

Author’s Profile

Arnab Roy Chowdhury

Arnab Roy Chowdhury is a UI developer by profession and a blogging enthusiast. He has been writing content for about 5 years and has strong expertise in technical blogs, travelogues, and content in the latest programming languages.

Got Questions? Drop them on LambdaTest Community. Visit now

Test your websites, web-apps or mobile apps seamlessly with LambdaTest.

  • Selenium, Cypress, Playwright & Puppeteer Testing
  • Real Devices Cloud
  • Native App Testing
  • Appium Testing
  • Live Interactive Testing
  • Smart Visual UI Testing

Book a Demo

JavaScript Errors Handbook

This README contains information that I’ve learned over the years about dealing with JavaScript errors, reporting them to the server, and navigating through a lot of bugs that can make this all really hard. Browsers have improved in this area, but there is still room left to improve to make sure that all applications can sanely and soundly handle any error that happens.

Test cases for content found in this guide can be found at https://mknichel.github.io/javascript-errors/.

Table of Contents

  • Introduction
  • Anatomy of a JavaScript Error
    • Producing a JavaScript Error
    • Error Messages
    • Stack Trace Format
  • Catching JavaScript Errors
    • window.onerror
    • try/catch
    • Protected Entry Points
    • Promises
    • Web Workers
    • Chrome Extensions

Introduction

Catching, reporting, and fixing errors is an important part of any application to ensure the health and stability of the application. Since JavaScript code is also executed on the client and in many different browser environments, staying on top of JS Errors from your application can also be hard. There are no formal web specs for how to report JS errors which cause differences in each browser’s implementation. Additionally, there have been many bugs in browsers’ implementation of JavaScript errors as well that have made this even harder. This page navigates through these aspects of JS Errors so that future developers can handle errors better and browsers will hopefully converge on standardized solutions.

Anatomy of a JavaScript Error

A JavaScript error is composed of two primary pieces: the error message and the stack trace. The error message is a string that describes what went wrong, and the stack trace describes where in the code the error happened. JS Errors can be produced either by the browser itself or thrown by application code.

Producing a JavaScript Error

A JS Error can be thrown by the browser when a piece of code doesn’t execute properly, or it can be thrown directly by code.

For example:

In this example, a variable that is actually a number can’t be invoked as a function. The browser will throw an error like TypeError: a is not a function with a stack trace that points to that line of code.

A developer might also want to throw an error in a piece of code if a certain precondition is not met. For example

if (!checkPrecondition()) {
  throw new Error("Doesn't meet precondition!");
}

In this case, the error will be Error: Doesn't meet precondition!. This error will also contain a stack trace that points to the appropriate line. Errors thrown by the browser and application code can be handled the same.

There are multiple ways that developers can throw an error in JavaScript:

  • throw new Error('Problem description.')
  • throw Error('Problem description.') <— equivalent to the first one
  • throw 'Problem description.' <— bad
  • throw null <— even worse

Throwing a string or null is really not recommended since the browser will not attach a stack trace to that error, losing the context of where that error ocurred in the code. It is best to throw an actual Error object, which will contain the error message as well as a stack trace that points to the right lines of code where the error happened.

Error Messages

Each browser has its own set of messages that it uses for the built in exceptions, such as the example above for trying to call a non-function. Browsers will try to use the same messages, but since there is no spec, this is not guaranteed. For example, both Chrome and Firefox use {0} is not a function for the above example while IE11 will report Function expected (notably also without reporting what variable was attempted to be called).

However, browsers tend to diverge often as well. When there are multiple default statements in a switch statement, Chrome will throw "More than one default clause in switch statement" while Firefox will report "more than one switch default". As new features are added to the web, these error messages have to be updated. These differences can come into play later when you are trying to handle reported errors from obfuscated code.

You can find the templates that browsers use for error messages at:

  • Firefox — http://mxr.mozilla.org/mozilla1.9.1/source/js/src/js.msg
  • Chrome — https://code.google.com/p/v8/source/browse/branches/bleeding_edge/src/messages.js
  • Internet Explorer — https://github.com/Microsoft/ChakraCore/blob/4e4d4f00f11b2ded23d1885e85fc26fcc96555da/lib/Parser/rterrors.h

error message warning Browsers will produce different error messages for some exceptions.

Stack Trace Format

The stack trace is a description of where the error happened in the code. It is composed of a series of frames, where each frames describe a particular line in the code. The topmost frame is the location where the error was thrown, while the subsequent frames are the function call stack — or how the code was executed to get to that point where the error was thrown. Since JavaScript is usually concatenated and minified, it is also important to have column numbers so that the exact statement can be located when a given line has a multitude of statements.

A basic stack trace in Chrome looks like:

  at throwError (http://mknichel.github.io/javascript-errors/throw-error-basic.html:8:9)
  at http://mknichel.github.io/javascript-errors/throw-error-basic.html:12:3

Each stack frame consists of a function name (if applicable and the code was not executed in the global scope), the script that it came from, and the line and column number of the code.

Unfortunately, there is no standard for the stack trace format so this differs by browser.

Microsoft Edge and IE 11’s stack trace looks similar to Chrome’s except it explicitly lists Global code:

  at throwError (http://mknichel.github.io/javascript-errors/throw-error-basic.html:8:3)
  at Global code (http://mknichel.github.io/javascript-errors/throw-error-basic.html:12:3)

Firefox’s stack trace looks like:

  throwError@http://mknichel.github.io/javascript-errors/throw-error-basic.html:8:9
  @http://mknichel.github.io/javascript-errors/throw-error-basic.html:12:3

Safari’s format is similar to Firefox’s format but is also slightly different:

  throwError@http://mknichel.github.io/javascript-errors/throw-error-basic.html:8:18
  global code@http://mknichel.github.io/javascript-errors/throw-error-basic.html:12:13

The same basic information is there, but the format is different.

Also note that in the Safari example, aside from the format being different than Chrome, the column numbers are different than both Chrome and Firefox. The column numbers also can deviate more in different error situations — for example in the code (function namedFunction() { throwError(); })();, Chrome will report the column for the throwError() function call while IE11 reports the column number as the start of the string. These differences will come back into play later when the server needs to parse the stack trace for reported errors and deobfuscate obfuscated stack traces.

See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/Stack for more information on the stack property of errors. When accessing the Error.stack property, Chrome does include the error message as part of the stack but Safari 10+ does not.

stack trace format warning The format of stack traces is different by browser in form and column numbers used.

Diving in more, there are a lot of nuances to stack trace formats that are discussed in the below sections.

Naming anonymous functions

By default, anonymous functions have no name and either appear as empty string or «Anonymous function» in the function names in the stack trace (depending on the browser). To improve debugging, you should add a name to all functions to ensure it appears in the stack frame. The easiest way to do this is to ensure that anonymous functions are specified with a name, even if that name is not used anywhere else. For example:

setTimeout(function nameOfTheAnonymousFunction() { ... }, 0);

This will cause the stack trace to go from:

at http://mknichel.github.io/javascript-errors/javascript-errors.js:125:17

to

at nameOfTheAnonymousFunction (http://mknichel.github.io/javascript-errors/javascript-errors.js:121:31)

In Safari, this would go from:

https://mknichel.github.io/javascript-errors/javascript-errors.js:175:27

to

nameOfTheAnonymousFunction@https://mknichel.github.io/javascript-errors/javascript-errors.js:171:41

This method ensures that nameOfTheAnonymousFunction appears in the frame for any code from inside that function, making debugging much easier. See http://www.html5rocks.com/en/tutorials/developertools/async-call-stack/#toc-debugging-tips for more information.

Assigning functions to a variable

Browsers will also use the name of the variable or property that a function is assigned to if the function itself does not have a name. For example, in

var fnVariableName = function() { ... };

browsers will use fnVariableName as the name of the function in stack traces.

    at throwError (http://mknichel.github.io/javascript-errors/javascript-errors.js:27:9)
    at fnVariableName (http://mknichel.github.io/javascript-errors/javascript-errors.js:169:37)

Even more nuanced than that, if this variable is defined within another function, all browsers will use just the name of the variable as the name of the function in the stack trace except for Firefox, which will use a different form that concatenates the name of the outer function with the name of the inner variable. Example:

function throwErrorFromInnerFunctionAssignedToVariable() {
  var fnVariableName = function() { throw new Error("foo"); };
  fnVariableName();
}

will produce in Firefox:

throwErrorFromInnerFunctionAssignedToVariable/fnVariableName@http://mknichel.github.io/javascript-errors/javascript-errors.js:169:37

In other browsers, this would look like:

at fnVariableName (http://mknichel.github.io/javascript-errors/javascript-errors.js:169:37)

inner function Firefox stack frame warning Firefox uses different stack frame text for functions defined within another function.

displayName Property

The display name of a function can also be set by the displayName property in all major browsers except for IE11. In these browsers, the displayName will appear in the devtools debugger, but in all browsers but Safari, it will not be used in Error stack traces (Safari differs from the rest by also using the displayName in the stack trace associated with an error).

var someFunction = function() {};
someFunction.displayName = " # A longer description of the function.";

There is no official spec for the displayName property, but it is supported by all the major browsers. See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/displayName and http://www.alertdebugging.com/2009/04/29/building-a-better-javascript-profiler-with-webkit/ for more information on displayName.

IE11 no displayName property IE11 doesn’t support the displayName property.

Safari displayName property bug Safari uses the displayName property as the symbol name in Error stack traces.

Programatically capturing stack traces

If an error is reported without a stack trace (see more details when this would happen below), then it’s possible to programatically capture a stack trace.

In Chrome, this is really easy to do by using the Error.captureStackTrace API. See https://github.com/v8/v8/wiki/Stack%20Trace%20API for more information on the use of this API.

For example:

function ignoreThisFunctionInStackTrace() {
  var err = new Error();
  Error.captureStackTrace(err, ignoreThisFunctionInStackTrace);
  return err.stack;
}

In other browsers, a stack trace can also be collected by creating a new error and accessing the stack property of that object:

var err = new Error('');
return err.stack;

However, IE10 only populates the stack trace when the error is actually thrown:

try {
  throw new Error('');
} catch (e) {
  return e.stack;
}

If none of these approaches work, then it’s possible to create a rough stack trace without line numbers or columns by iterating over the arguments.callee.caller object — this won’t work in ES5 Strict Mode though and it’s not a recommended approach.

Async stack traces

It is very common for asynchronous points to be inserted into JavaScript code, such as when code uses setTimeout or through the use of Promises. These async entry points can cause problems for stack traces, since they cause a new execution context to form and the stack trace starts from scratch again.

Chrome DevTools has support for async stack traces, or in other words making sure the stack trace of an error also shows the frames that happened before the async point was introduced. With the use of setTimeout, this will capture who called the setTimeout function that eventually produced an error. See http://www.html5rocks.com/en/tutorials/developertools/async-call-stack/ for more information.

An async stack trace will look like:

  throwError	@	throw-error.js:2
  setTimeout (async)		
  throwErrorAsync	@	throw-error.js:10
  (anonymous function)	@	throw-error-basic.html:14

Async stack traces are only supported in Chrome DevTools right now, only for exceptions that are thrown when DevTools are open. Stack traces accessed from Error objects in code will not have the async stack trace as part of it.

It is possible to polyfill async stack traces in some cases, but this could cause a significant performance hit for your application since capturing a stack trace is not cheap.

Only Chrome supports async stack traces Only Chrome DevTools natively supports async stack traces.

Naming inline scripts and eval

Stack traces for code that was eval’ed or inlined into a HTML page will use the page’s URL and line/column numbers for the executed code.

For example:

  at throwError (http://mknichel.github.io/javascript-errors/throw-error-basic.html:8:9)
  at http://mknichel.github.io/javascript-errors/throw-error-basic.html:12:3

If these scripts actually come from a script that was inlined for optimization reasons, then the URL, line, and column numbers will be wrong. To work around this problem, Chrome and Firefox support the //# sourceURL= annotation (Safari, Edge, and IE do not). The URL specified in this annotation will be used as the URL for all stack traces, and the line and column number will be computed relative to the start of the <script> tag instead of the HTML document. For the same error as above, using the sourceURL annotation with a value of «inline.js» will produce a stack trace that looks like:

  at throwError (http://mknichel.github.io/javascript-errors/inline.js:8:9)
  at http://mknichel.github.io/javascript-errors/inline.js:12:3

This is a really handy technique to make sure that stack traces are still correct even when using inline scripts and eval.

http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl describes the sourceURL annotation in more detail.

Lack of sourceURL support Safari, Edge, and IE do not support the sourceURL annotation for naming inline scripts and evals. If you use inline scripts in IE or Safari and you obfuscate your code, you will not be able to deobfuscate errors that come from those scripts.

Chrome bug for computing line numbers with sourceURL Up until Chrome 42, Chrome did not compute line numbers correctly for inline scripts that use the sourceURL annotation. See https://bugs.chromium.org/p/v8/issues/detail?id=3920 for more information.

Chrome bug in line numbers from inline scripts Line numbers for stack frames from inline scripts are incorrect when the sourceURL annotation is used since they are relative to the start of the HTML document instead of the start of the inline script tag (making correct deobfuscation not possible). https://code.google.com/p/chromium/issues/detail?id=578269

Eval stack traces

For code that uses eval, there are other differences in the stack trace besides whether or not it uses the sourceURL annotation. In Chrome, a stack trace from a statement used in eval could look like:

Error: Error from eval
    at evaledFunction (eval at evalError (http://mknichel.github.io/javascript-errors/javascript-errors.js:137:3), <anonymous>:1:36)
    at eval (eval at evalError (http://mknichel.github.io/javascript-errors/javascript-errors.js:137:3), <anonymous>:1:68)
    at evalError (http://mknichel.github.io/javascript-errors/javascript-errors.js:137:3)

In MS Edge and IE11, this would look like:

Error from eval
    at evaledFunction (eval code:1:30)
    at eval code (eval code:1:2)
    at evalError (http://mknichel.github.io/javascript-errors/javascript-errors.js:137:3)

In Safari:

Error from eval
    evaledFunction
    eval code
    eval@[native code]
    evalError@http://mknichel.github.io/javascript-errors/javascript-errors.js:137:7

and in Firefox:

Error from eval
    evaledFunction@http://mknichel.github.io/javascript-errors/javascript-errors.js line 137 > eval:1:36
    @http://mknichel.github.io/javascript-errors/javascript-errors.js line 137 > eval:1:11
    evalError@http://mknichel.github.io/javascript-errors/javascript-errors.js:137:3

These differences can make it hard to parse eval code the same across all browsers.

Different eval stack trace format across browsers Each browser uses a different stack trace format for errors that happened inside eval.

Stack traces with native frames

Your JavaScript code can also be called directly from native code. Array.prototype.forEach is a good example — you pass a function to forEach and the JS engine will call that function for you.

function throwErrorWithNativeFrame() {
  var arr = [0, 1, 2, 3];
  arr.forEach(function namedFn(value) {
    throwError();
  });
}

This produces different stack traces in different browsers. Chrome and Safari append the name of the native function in the stack trace itself as a separate frame, such as:

(Chrome)
    at namedFn (http://mknichel.github.io/javascript-errors/javascript-errors.js:153:5)
    at Array.forEach (native)
    at throwErrorWithNativeFrame (http://mknichel.github.io/javascript-errors/javascript-errors.js:152:7)

(Safari)
    namedFn@http://mknichel.github.io/javascript-errors/javascript-errors.js:153:15
    forEach@[native code]
    throwErrorWithNativeFrame@http://mknichel.github.io/javascript-errors/javascript-errors.js:152:14

(Edge)
    at namedFn (http://mknichel.github.io/javascript-errors/javascript-errors.js:153:5)
    at Array.prototype.forEach (native code)
    at throwErrorWithNativeFrame (http://mknichel.github.io/javascript-errors/javascript-errors.js:152:7)

However, Firefox and IE11 do not show that forEach was called as part of the stack:

namedFn@http://mknichel.github.io/javascript-errors/javascript-errors.js:153:5
throwErrorWithNativeFrame@http://mknichel.github.io/javascript-errors/javascript-errors.js:152:3

Different native function stack frame behavior Some browsers include native code frames in stack traces, while others do not.

Catching JavaScript Errors

To detect that your application had an error, some code must be able to catch that error and report about it. There are multiple techniques for catching errors, each with their pros and cons.

window.onerror

window.onerror is one of the easiest and best ways to get started catching errors. By assigning window.onerror to a function, any error that is uncaught by another part of the application will be reported to this function, along with some information about the error. For example:

window.onerror = function(msg, url, line, col, err) {
  console.log('Application encountered an error: ' + msg);
  console.log('Stack trace: ' + err.stack);
}

https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/onerror describes this in more detail.

Historically, there have been a few problems with this approach:

No Error object provided

The 5th argument to the window.onerror function is supposed to be an Error object. This was added to the WHATWG spec in 2013: https://html.spec.whatwg.org/multipage/webappapis.html#errorevent. Chrome, Firefox, and IE11 now properly provide an Error object (along with the critical stack property), but Safari, MS Edge, and IE10 do not. This works in Firefox since Firefox 14 (https://bugzilla.mozilla.org/show_bug.cgi?id=355430) and in Chrome since late 2013 (https://mikewest.org/2013/08/debugging-runtime-errors-with-window-onerror, https://code.google.com/p/chromium/issues/detail?id=147127). Safari 10 launched support for the Error object in window.onerror.

Lack of support for Error in window.onerror Safari (versions below 10), MS Edge, and IE10 do not support an Error object with a stack trace in window.onerror.

Cross domain sanitization

In Chrome, errors that come from another domain in the window.onerror handler will be sanitized to «Script error.», «», 0. This is generally okay if you really don’t want to process the error if it comes from a script that you don’t care about, so the application can filter out errors that look like this. However, this does not happen in Firefox or Safari or IE11, nor does Chrome do this for try/catch blocks that wrap the offending code.

If you would like to receive errors in window.onerror in Chrome with full fidelity from cross domain scripts, those resources must provide the appropriate cross origin headers. See https://mikewest.org/2013/08/debugging-runtime-errors-with-window-onerror for more information.

Cross domain sanitization in window.onerror Chrome is the only browser that will sanitize errors that come from another origin. Take care to filter these out, or set the appropriate headers.

Chrome Extensions

In old versions of Chrome, Chrome extensions that are installed on a user’s machine could also throw errors that get reported to window.onerror. This has been fixed in newer versions of Chrome. See the dedicated Chrome Extensions section below.

window.addEventListener(«error»)

The window.addEventListener("error") API works the same as the window.onerror API. See http://www.w3.org/html/wg/drafts/html/master/webappapis.html#runtime-script-errors for more information on this approach.

Showing errors in DevTools console for development

Catching errors via window.onerror does not prevent that error from also appearing in the DevTools console. This is most likely the right behavior for development since the developer can easily see the error. If you don’t want these errors to show up in production to end users, e.preventDefault() can be called if using the window.addEventListener approach.

Recommendation

window.onerror is the best tool to catch and report JS errors. It’s recommended that only JS errors with valid Error objects and stack traces are reported back to the server, otherwise the errors may be hard to investigate or you may get a lot of spam from Chrome extensions or cross domain scripts.

try/catch

Given the above section, unfortunately it’s not possible to rely on window.onerror in all browsers to capture all error information. For catching exceptions locally, a try/catch block is the obvious choice. It’s also possible to wrap entire JavaScript files in a try/catch block to capture error information that can’t be caught with window.onerror. This improves the situations for browsers that don’t support window.onerror, but also has some downsides.

Doesn’t catch all errors

A try/catch block won’t capture all errors in a program, such as errors that are thrown from an async block of code through window.setTimeout. Try/catch can be used with Protected Entry Points to help fill in the gaps.

Use protected entry points with try/catch try/catch blocks wrapping the entire application aren’t sufficient to catch all errors.

Deoptimizations

Old versions of V8 (and potentially other JS engines), functions that contain a try/catch block won’t be optimized by the compiler (http://www.html5rocks.com/en/tutorials/speed/v8/). Chrome fixed this in TurboFan (https://codereview.chromium.org/1996373002).

Protected Entry Points

An «entry point» into JavaScript is any browser API that can start execution of your code. Examples include setTimeout, setInterval, event listeners, XHR, web sockets, or promises. Errors that are thrown from these entry points will be caught by window.onerror, but in the browsers that don’t support the full Error object in window.onerror, an alternative mechanism is needed to catch these errors since the try/catch method mentioned above won’t catch them either.

Thankfully, JavaScript allows these entry points to be wrapped so that a try/catch block can be inserted before the function is invoked to catch any errors thrown by the code.

Each entry point will need slightly different code to protect the entry point, but the gist of the methodology is:

function protectEntryPoint(fn) {
  return function protectedFn() {
    try {
      return fn();
    } catch (e) {
      // Handle error.
    }
  }
}
_oldSetTimeout = window.setTimeout;
window.setTimeout = function protectedSetTimeout(fn, time) {
  return _oldSetTimeout.call(window, protectEntryPoint(fn), time);
};

Promises

Sadly, it’s easy for errors that happen in Promises to go unobserved and unreported. Errors that happen in a Promise but are not handled by attaching a rejection handler are not reported anywhere else — they do not get reported to window.onerror. Even if a Promise attaches a rejection handler, that code itself must manually report those errors for them to be logged. See http://www.html5rocks.com/en/tutorials/es6/promises/#toc-error-handling for more information. For example:

window.onerror = function(...) {
  // This will never be invoked by Promise code.
};

var p = new Promise(...);
p.then(function() {
  throw new Error("This error will be not handled anywhere.");
});

var p2 = new Promise(...);
p2.then(function() {
  throw new Error("This error will be handled in the chain.");
}).catch(function(error) {
  // Show error message to user
  // This code should manually report the error for it to be logged on the server, if applicable.
});

One approach to capture more information is to use Protected Entry Points to wrap invocations of Promise methods with a try/catch to report errors. This might look like:

  var _oldPromiseThen = Promise.prototype.then;
  Promise.prototype.then = function protectedThen(callback, errorHandler) {
    return _oldPromiseThen.call(this, protectEntryPoint(callback), protectEntryPoint(errorHandler));
  };

Errors in Promises will go unhandled by default Sadly, errors from Promises will go unhandled by default.

Error handling in Promise polyfills

Promise implementations, such as Q, Bluebird, and Closure handle errors in different ways which are better than the error handling in the browser implementation of Promises.

  • In Q, you can «end» the Promise chain by calling .done() which will make sure that if an error wasn’t handled in the chain, it will get rethrown and reported. See https://github.com/kriskowal/q#handling-errors
  • In Bluebird, unhandled rejections are logged and reported immediately. See http://bluebirdjs.com/docs/features.html#surfacing-unhandled-errors
  • In Closure’s goog.Promise implementation, unhandled rejections are logged and reported if no chain in the Promise handles the rejection within a configurable time interval (in order to allow code later in the program to add a rejection handler).

Long stack traces

The async stack trace section above discusses that browsers don’t capture stack information when there is an async hook, such as calling Promise.prototype.then. Promise polyfills feature a way to capture the async stack trace points which can make diagnosing errors much easier. This approach is expensive, but it can be really useful for capturing more debug information.

  • In Q, call Q.longStackSupport = true;. See https://github.com/kriskowal/q#long-stack-traces
  • In Bluebird, call Promise.longStackTraces() somewhere in the application. See http://bluebirdjs.com/docs/features.html#long-stack-traces.
  • In Closure, set goog.Promise.LONG_STACK_TRACES to true.

Promise Rejection Events

Chrome 49 added support for events that are dispatched when a Promise is rejected. This allows applications to hook into Promise errors to ensure that they get centrally reported along with the rest of the errors.

window.addEventListener('unhandledrejection', event => {
  // event.reason contains the rejection reason. When an Error is thrown, this is the Error object.
});

See https://googlechrome.github.io/samples/promise-rejection-events/ and https://www.chromestatus.com/feature/4805872211460096 for more information.

This is not supported in any other browser.

Web Workers

Web workers, including dedicated workers, shared workers, and service workers, are becoming more popular in applications today. Since all of these workers are separate scripts from the main page, they each need their own error handling code. It is recommended that each worker script install its own error handling and reporting code for maximum effectiveness handling errors from workers.

Dedicated workers

Dedicated web workers execute in a different execution context than the main page, so errors from workers aren’t caught by the above mechanisms. Additional steps need to be taken to capture errors from workers on the page.

When a worker is created, the onerror property can be set on the new worker:

var worker = new Worker('worker.js');
worker.onerror = function(errorEvent) { ... };

This is defined in https://html.spec.whatwg.org/multipage/workers.html#handler-abstractworker-onerror. The onerror function on the worker has a different signature than the window.onerror discussed above. Instead of accepting 5 arguments, worker.onerror takes a single argument: an ErrorEvent object. The API for this object can be found at https://developer.mozilla.org/en-US/docs/Web/API/ErrorEvent. It contains the message, filename, line, and column, but no stable browser today contains the «Error» object that contains the stack trace (errorEvent.error is null). Since this API is executed in the parent page’s scope, it would be useful for using the same reporting mechanism as the parent page; unfortunately due to the lack of a stack trace, this API is of limited use.

Inside of the JS run by the worker, you can also define an onerror API that follows the usual window.onerror API: https://html.spec.whatwg.org/multipage/webappapis.html#onerroreventhandler. In the worker code:

self.onerror = function(message, filename, line, col, error) { ... };

The discussion of this API mostly follows the discussion above for window.onerror. However, there are 2 notable things to point out:

Firefox and Safari do not report the «error» object as the 5th argument to the function, so these browsers do not get a stack trace from the worker (Chrome, MS Edge, and IE11 do get a stack trace). Protected Entry Points for the onmessage function within the worker can be used to capture stack trace information for these browsers.

Since this code executes within the worker, the code must choose how to report the error back to the server: It must either use postMessage to communicate the error back to the parent page, or install an XHR error reporting mechanism (discussed more below) in the worker itself.

In Firefox, Safari, and IE11 (but not in Chrome), the parent page’s window.onerror function will also be called after the worker’s own onerror and the onerror event listener set by the page has been called. However, this window.onerror will also not contain an error object and therefore won’t have a stack trace also. These browsers must also take care to not report errors from workers multiple times.

Shared workers

Chrome and Firefox support the SharedWorker API for sharing a worker among multiple pages. Since the worker is shared, it is not attached to one parent page exclusively; this leads to some differences in how errors are handled, although SharedWorker mostly follows the same information as the dedicated web worker.

In Chrome, when there is an error in a SharedWorker, only the worker’s own error handling within the worker code itself will be called (like if they set self.onerror). The parent page’s window.onerror will not be called, and Chrome does not support the inherited AbstractWorker.onerror that can be called in the parent page as defined in the spec.

In Firefox, this behavior is different. An error in the shared worker will cause the parent page’s window.onerror to be called, but the error object will be null. Additionally, Firefox does support the AbstractWorker.onerror property, so the parent page can attach an error handler of its own to the worker. However, when this error handler is called, the error object will be null so there will be no stack trace, so it’s of limited use.

Error handling for shared workers differs by browser.

Service Workers

Service Workers are a brand new spec that is currently only available in recent Chrome and Firefox versions. These workers follow the same discussion as dedicated web workers.

Service workers are installed by calling the navigator.serviceWorker.register function. This function returns a Promise which will be rejected if there was an error installing the service worker, such as it throwing an error during initialization. This error will only contain a string message and nothing else. Additionally, since Promises don’t report errors to window.onerror handlers, the application itself would have to add a catch block to the Promise to catch the error.

navigator.serviceWorker.register('service-worker-installation-error.js').catch(function(error) {
  // error typeof string
});

Just like the other workers, service workers can set a self.onerror function within the service workers to catch errors. Installation errors in the service worker will be reported to the onerror function, but unfortunately they won’t contain an error object or stack trace.

The service worker API contains an onerror property inherited from the AbstractWorker interface, but Chrome does not do anything with this property.

Worker Try/Catch

To capture stack traces in Firefox + Safari within a worker, the onmessage function can be wrapped in a try/catch block to catch any errors that propagate to the top.

self.onmessage = function(event) {
  try {
    // logic here
  } catch (e) {
    // Report exception.
  }
};

The normal try/catch mechanism will capture stack traces for these errors, producing an exception that looks like:

Error from worker
throwError@http://mknichel.github.io/javascript-errors/worker.js:4:9
throwErrorWrapper@http://mknichel.github.io/javascript-errors/worker.js:8:3
self.onmessage@http://mknichel.github.io/javascript-errors/worker.js:14:7

Chrome Extensions

Chrome Extensions deserve their own section since errors in these scripts can operate slightly differently, and historically (but not anymore) errors from Chrome Extensions have also been a problem for large popular sites.

Content Scripts

Content scripts are scripts that run in the context of web pages that a user visits. These scripts run in an isolated execution environment so they can access the DOM but they can not access JavaScript on the parent page (and vice versa).

Since content scripts have their own execution environment, they can assign to the window.onerror handler in their own script and it won’t affect the parent page. However, errors caught by window.onerror in the content script are sanitized by Chrome resulting in a «Script error.» with null filename and 0 for line and column. This bug is tracked by https://code.google.com/p/chromium/issues/detail?id=457785. Until that bug is fixed, a try/catch block or protected entry points are the only ways to catch JS errors in a content script with stack traces.

In years past, errors from content scripts would be reported to the window.onerror handler of the parent page which could result in a large amount of spammy error reports for popular sites. This was fixed in late 2013 though (https://code.google.com/p/chromium/issues/detail?id=225513).

Errors in Chrome Extensions are sanitized before being handled by window.onerror.

Browser Actions

Chrome extensions can also generate browser action popups, which are small HTML pages that spawn when a user clicks a Chrome extension icon to the right of the URL bar. These pages can also run JavaScript, in an entirely different execution environment from everything else. window.onerror works properly for this JavaScript.

Reporting Errors to the Server

Once the client is configured to properly catch exceptions with correct stack traces, these exceptions should be reported back to the server so they can be tracked, analyzed, and then fixed. Typically this is done with a XHR endpoint that records the error message and the stack trace information, along with any relevant client context information, such as the version of the code that’s running, the user agent, the user’s locale, and the top level URL of the page.

If the application uses multiple mechanisms to catch errors, it’s important to not report the same error twice. Errors that contain a stack trace should be preferred; errors reported without a stack trace can be hard to track down in a large application.

Понравилась статья? Поделить с друзьями:
  • Javascript error opera
  • Javascript error occurred in the main process перевод
  • Javascript error occurred in the main process как исправить teams
  • Javascript error occurred in the main process median xl
  • Javascript error occurred in the main process linux