This commit is contained in:
Martin Donnelly 2016-05-27 09:51:36 +01:00
commit 9b5925625d
547 changed files with 84553 additions and 0 deletions

5
.bowerrc Normal file
View File

@ -0,0 +1,5 @@
{
"directory": "app/libs/",
"timeout": 120000
}

32
.editorconfig Normal file
View File

@ -0,0 +1,32 @@
; http://editorconfig.org
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 2
[*.txt]
insert_final_newline = false
trim_trailing_whitespace = false
[*.py]
indent_size = 4
[*.m]
indent_size = 4
[Makefile]
indent_style = tab
indent_size = 8
[*.{js,json}]
indent_style = space
indent_size = 2
[*.md]
trim_trailing_whitespace = false

180
.gitignore vendored Normal file
View File

@ -0,0 +1,180 @@
# Created by .ignore support plugin (hsz.mobi)
### Archives template
# It's better to unpack these files and commit the raw source because
# git has its own built in compression methods.
*.7z
*.jar
*.rar
*.zip
*.gz
*.bzip
*.bz2
*.xz
*.lzma
*.cab
#packing-only formats
*.iso
*.tar
#package management formats
*.dmg
*.xpi
*.gem
*.egg
*.deb
*.rpm
*.msi
*.msm
*.msp
### Windows template
# Windows image file caches
Thumbs.db
ehthumbs.db
# Folder config file
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msm
*.msp
# Windows shortcuts
*.lnk
### OSX template
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### JetBrains template
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio
*.iml
## Directory-based project format:
.idea/
# if you remove the above rule, at least ignore the following:
# User-specific stuff:
# .idea/workspace.xml
# .idea/tasks.xml
# .idea/dictionaries
# Sensitive or high-churn files:
# .idea/dataSources.ids
# .idea/dataSources.xml
# .idea/sqlDataSources.xml
# .idea/dynamic.xml
# .idea/uiDesigner.xml
# Gradle:
# .idea/gradle.xml
# .idea/libraries
# Mongo Explorer plugin:
# .idea/mongoSettings.xml
## File-based project format:
*.ipr
*.iws
## Plugin-specific files:
# IntelliJ
/out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
### Node template
# Logs
logs
*.log
npm-debug.log*
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directory
# https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
node_modules
bower_components
### VisualStudioCode template
.settings
### Xcode template
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
## Build generated
build/
DerivedData
## Various settings
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
## Other
*.xccheckout
*.moved-aside
*.xcuserstate
dist

46
.jscsrc Normal file
View File

@ -0,0 +1,46 @@
{
"disallowKeywords": ["with"],
"disallowKeywordsOnNewLine": ["else"],
"disallowMixedSpacesAndTabs": true,
"disallowMultipleVarDecl": "exceptUndefined",
"disallowNewlineBeforeBlockStatements": true,
"disallowQuotedKeysInObjects": true,
"disallowSpaceAfterObjectKeys": true,
"disallowSpaceAfterPrefixUnaryOperators": true,
"disallowSpacesInFunction": {
"beforeOpeningRoundBrace": true
},
"disallowSpacesInsideParentheses": true,
"disallowTrailingWhitespace": true,
"maximumLineLength": 160,
"requireCamelCaseOrUpperCaseIdentifiers": false,
"requireCapitalizedComments": true,
"requireCapitalizedConstructors": true,
"requireCurlyBraces": true,
"requireSpaceAfterKeywords": [
"if",
"else",
"for",
"while",
"do",
"switch",
"case",
"return",
"try",
"catch",
"typeof"
],
"requireSpaceAfterLineComment": true,
"requireSpaceAfterBinaryOperators": true,
"requireSpaceBeforeBinaryOperators": true,
"requireSpaceBeforeBlockStatements": true,
"requireSpaceBeforeObjectValues": true,
"requireSpacesInFunction": {
"beforeOpeningCurlyBrace": true
},
"requireTrailingComma": false,
"requireEarlyReturn": false,
"validateIndentation": 2,
"validateLineBreaks": "LF",
"validateQuoteMarks": "'"
}

37
.jshintrc Normal file
View File

@ -0,0 +1,37 @@
{
"predef": [
"Promise",
"$"
],
"globals": {
"$": false,
"MicroEvent": false
},
"node":true,
"browser": true,
"boss": true,
"curly": true,
"debug": false,
"devel": true,
"eqeqeq": true,
"evil": true,
"forin": false,
"immed": false,
"laxbreak": false,
"newcap": true,
"noarg": true,
"noempty": false,
"nonew": false,
"nomen": false,
"onevar": false,
"plusplus": false,
"regexp": false,
"undef": true,
"sub": true,
"strict": false,
"white": false,
"eqnull": true,
"esnext": true,
"unused": true,
"supernew":true
}

307
app/css/app.css Normal file
View File

@ -0,0 +1,307 @@
body {
font-family: Ubuntu, "Helvetica Neue", Helvetica, arial, sans-serif;
background-color: #004c6d;
}
#weatherIcon {
margin-left:25%;
margin-right:25%;
height:70px;
width:70px;
}
#lightR, #projR { color: red !important; }
#lightG, #projG { color: green !important; }
#lightB, #projB { color: blue !important; }
#lightW, #projW { background-color: #aabbcc; }
.lightBG, .heatingBG, .projectorBG {
float: right;
}
/*.lightBG {
background-color: rgba(255, 255, 0, 0.3);
}
.heatingBG {
background-color: rgba(255, 0, 255, 0.3);
}
.projectorBG {
background-color: rgba(0, 255, 255, 0.3);
}*/
.mui-panel {
background-color: #015579;
}
.h105 {
height: 100px;
}
.mdHeading {
overflow: hidden;
}
.mui--text-title {
color: #ffffff;
}
.item_content {
height: 100px;
/* border: 1px solid grey;*/
min-height: 100px;
overflow: hidden;
}
.item_content a.title {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: #ffffff;
}
.item_content div.body, .item_content div.site, .item_content div.tags {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: #313131;
}
.time, .date, .temp {
font-family: 'Ubuntu Condensed', sans-serif;
font-size: 80px;
color: #bad649;
}
.time span.hour:after {
content: ":";
}
.date {
font-size: 35px;
line-height: 1;
}
.temp::after {
content: "°c";
}
.item_content div.tags {
color: blue;
}
.noConnection {
color: rgb(244, 150, 26);
}
#caltext {
color: #fff;
}
/* Smartphones (portrait and landscape) ----------- */
@media only screen and (min-device-width: 320px) and (max-device-width: 480px) {
/* Styles */
.time, .date, .temp {
font-family: 'Ubuntu Condensed', sans-serif;
font-size: 33px;
/*color: #ff0000;*/
}
.time {
font-size: 50px;
line-height: 1;
}
.time span.hour:after {
content: "\a";
white-space: pre;
}
.temp {
font-size: 70px;
}
.temp::after {
content: "°";
}
.wd-we {
font-size: 75%;
}
.mo {
font-size: 85%;
}
.mo.mo-1, .mo.mo-10 {
font-size: 70%;
}
.mo.mo-2 {
font-size: 65%;
}
.mo.mo-8 {
font-size: 80%;
}
.mo.mo-9 {
font-size: 55%;
}
.mo.mo-11, .mo.mo-12 {
font-size: 60%;
}
}
/* Smartphones (landscape) ----------- */
@media only screen and (min-width: 321px) {
/* Styles */
}
/* Smartphones (portrait) ----------- */
@media only screen and (max-width: 320px) {
/* Styles */
}
.spinner {
margin: 25px auto 0;
width: 70px;
text-align: center;
}
.spinner > div {
width: 18px;
height: 18px;
background-color: rgb(244, 150, 26);
border-radius: 100%;
display: inline-block;
-webkit-animation: sk-bouncedelay 1.4s infinite ease-in-out both;
animation: sk-bouncedelay 1.4s infinite ease-in-out both;
}
.spinner .bounce1 {
-webkit-animation-delay: -0.32s;
animation-delay: -0.32s;
}
.spinner .bounce2 {
-webkit-animation-delay: -0.16s;
animation-delay: -0.16s;
}
@-webkit-keyframes sk-bouncedelay {
0%, 80%, 100% { -webkit-transform: scale(0) }
40% { -webkit-transform: scale(1.0) }
}
@keyframes sk-bouncedelay {
0%, 80%, 100% {
-webkit-transform: scale(0);
transform: scale(0);
}
40% {
-webkit-transform: scale(1.0);
transform: scale(1.0);
}
}
.material-icons {
color: #e5f7fd;
}
.material-icons.md-18 { font-size: 18px; }
.material-icons.md-24 { font-size: 24px; }
.material-icons.md-36 { font-size: 36px; }
.material-icons.md-48 { font-size: 48px; }
.material-icons.md-100 { font-size: 100px; }
/* Rules for using icons as black on a light background. */
.material-icons.md-dark { color: rgba(0, 0, 0, 0.54); }
.material-icons.md-dark.md-inactive { color: rgba(0, 0, 0, 0.26); }
/* Rules for using icons as white on a dark background. */
.material-icons.md-light { color: rgba(255, 255, 255, 1); }
.material-icons.md-light.md-inactive { color: rgba(255, 255, 255, 0.3); }
.material-icons.md-bulb {
content: ""
}
/*
fan : toys
<i class="material-icons">&#xE332;</i>
bulb : lightbulb_outline
<i class="material-icons">&#xE90F;</i>
calendar: event_note
<i class="material-icons">&#xE616;</i>
projector: cast
<i class="material-icons">&#xE307;</i>
*/
.md-display {
opacity: 1;
transition: opacity 0.3s, visibility 0.3s;
}
.lostConnection {
opacity: 0.5;
transition: opacity 0.3s, visibility 0.3s;
}
.mui-ellipsis-2 {
display: -webkit-box;
overflow: hidden;
text-overflow: ellipsis;
word-wrap: break-word;
white-space: normal!important;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
}
.pulser {
display: block;
margin-top:10%;
border-radius: 100px;
width: 30px;
height: 30px;
border: 10px solid #C5F4EB;
-webkit-animation: pulse 0.75s ease-in infinite;
-moz-animation: pulse 0.75s ease-in infinite;
animation: pulse 0.75s ease-in infinite;
}
@-webkit-keyframes pulse {
0% { -webkit-transform: scale(0); }
85% { opacity: 1; }
100% { -webkit-transform: scale(1); -webkit-filter: blur(5px); opacity: 0; }
}
@-moz-keyframes pulse {
0% { -moz-transform: scale(0); }
85% { opacity: 1; }
100% { -moz-transform: scale(1); -moz-filter: blur(5px); opacity: 0; }
}
@keyframes pulse {
0% { transform: scale(0); }
85% { opacity: 1; }
100% { transform: scale(1); filter: blur(5px); opacity: 0; }
}

115
app/css/index.css Normal file
View File

@ -0,0 +1,115 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
* {
-webkit-tap-highlight-color: rgba(0,0,0,0); /* make transparent link selection, adjust last value opacity 0 to 1.0 */
}
body {
-webkit-touch-callout: none; /* prevent callout to copy image, etc when tap to hold */
-webkit-text-size-adjust: none; /* prevent webkit from resizing text to fit */
-webkit-user-select: none; /* prevent copy paste, to allow, change 'none' to 'text' */
background-color:#E4E4E4;
background-image:linear-gradient(top, #A7A7A7 0%, #E4E4E4 51%);
background-image:-webkit-linear-gradient(top, #A7A7A7 0%, #E4E4E4 51%);
background-image:-ms-linear-gradient(top, #A7A7A7 0%, #E4E4E4 51%);
background-image:-webkit-gradient(
linear,
left top,
left bottom,
color-stop(0, #A7A7A7),
color-stop(0.51, #E4E4E4)
);
background-attachment:fixed;
font-family:'HelveticaNeue-Light', 'HelveticaNeue', Helvetica, Arial, sans-serif;
font-size:12px;
height:100%;
margin:0px;
padding:0px;
text-transform:uppercase;
width:100%;
}
/* Portrait layout (default) */
.app {
background:url(../img/logo.png) no-repeat center top; /* 170px x 200px */
position:absolute; /* position in the center of the screen */
left:50%;
top:50%;
height:50px; /* text area height */
width:225px; /* text area width */
text-align:center;
padding:180px 0px 0px 0px; /* image height is 200px (bottom 20px are overlapped with text) */
margin:-115px 0px 0px -112px; /* offset vertical: half of image height and text area height */
/* offset horizontal: half of text area width */
}
/* Landscape layout (with min-width) */
@media screen and (min-aspect-ratio: 1/1) and (min-width:400px) {
.app {
background-position:left center;
padding:75px 0px 75px 170px; /* padding-top + padding-bottom + text area = image height */
margin:-90px 0px 0px -198px; /* offset vertical: half of image height */
/* offset horizontal: half of image width and text area width */
}
}
h1 {
font-size:24px;
font-weight:normal;
margin:0px;
overflow:visible;
padding:0px;
text-align:center;
}
.event {
border-radius:4px;
-webkit-border-radius:4px;
color:#FFFFFF;
font-size:12px;
margin:0px 30px;
padding:2px 0px;
}
.event.listening {
background-color:#333333;
display:block;
}
.event.received {
background-color:#4B946A;
display:none;
}
@keyframes fade {
from { opacity: 1.0; }
50% { opacity: 0.4; }
to { opacity: 1.0; }
}
@-webkit-keyframes fade {
from { opacity: 1.0; }
50% { opacity: 0.4; }
to { opacity: 1.0; }
}
.blink {
animation:fade 3000ms infinite;
-webkit-animation:fade 3000ms infinite;
}

View File

@ -0,0 +1,15 @@
.material-icons {
font-family: 'Material Icons';
font-weight: normal;
font-style: normal;
font-size: 24px;
line-height: 1;
letter-spacing: normal;
text-transform: none;
display: inline-block;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
}

1912
app/css/mui.css Normal file

File diff suppressed because it is too large Load Diff

1912
app/css/mui.custom.css Normal file

File diff suppressed because it is too large Load Diff

1
app/css/mui.min.css vendored Normal file

File diff suppressed because one or more lines are too long

94
app/css/progress.css Normal file
View File

@ -0,0 +1,94 @@
/* Progress Bar */
.progress {
position: relative;
height: 4px;
display: block;
width: 100%;
background-color: #acece6;
border-radius: 2px;
background-clip: padding-box;
/* margin: 0.5rem 0 1rem 0; */
overflow: hidden; }
.progress .determinate {
position: absolute;
background-color: inherit;
top: 0;
bottom: 0;
background-color: #26a69a;
transition: width .3s linear; }
.progress .indeterminate {
background-color: #26a69a; }
.progress .indeterminate:before {
content: '';
position: absolute;
background-color: inherit;
top: 0;
left: 0;
bottom: 0;
will-change: left, right;
-webkit-animation: indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite;
animation: indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite; }
.progress .indeterminate:after {
content: '';
position: absolute;
background-color: inherit;
top: 0;
left: 0;
bottom: 0;
will-change: left, right;
-webkit-animation: indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite;
animation: indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite;
-webkit-animation-delay: 1.15s;
animation-delay: 1.15s; }
@-webkit-keyframes indeterminate {
0% {
left: -35%;
right: 100%; }
60% {
left: 100%;
right: -90%; }
100% {
left: 100%;
right: -90%; }
}
@keyframes indeterminate {
0% {
left: -35%;
right: 100%; }
60% {
left: 100%;
right: -90%; }
100% {
left: 100%;
right: -90%; }
}
@-webkit-keyframes indeterminate-short {
0% {
left: -200%;
right: 100%; }
60% {
left: 107%;
right: -8%; }
100% {
left: 107%;
right: -8%; }
}
@keyframes indeterminate-short {
0% {
left: -200%;
right: 100%; }
60% {
left: 107%;
right: -8%; }
100% {
left: 107%;
right: -8%; }
}

195
app/css/ripple.css Normal file
View File

@ -0,0 +1,195 @@
@-webkit-keyframes uil-ripple {
0% {
width: 0;
height: 0;
opacity: 0;
margin: 0 0 0 0;
}
33% {
width: 44%;
height: 44%;
margin: -22% 0 0 -22%;
opacity: 1;
}
100% {
width: 88%;
height: 88%;
margin: -44% 0 0 -44%;
opacity: 0;
}
}
@-webkit-keyframes uil-ripple {
0% {
width: 0;
height: 0;
opacity: 0;
margin: 0 0 0 0;
}
33% {
width: 44%;
height: 44%;
margin: -22% 0 0 -22%;
opacity: 1;
}
100% {
width: 88%;
height: 88%;
margin: -44% 0 0 -44%;
opacity: 0;
}
}
@-moz-keyframes uil-ripple {
0% {
width: 0;
height: 0;
opacity: 0;
margin: 0 0 0 0;
}
33% {
width: 44%;
height: 44%;
margin: -22% 0 0 -22%;
opacity: 1;
}
100% {
width: 88%;
height: 88%;
margin: -44% 0 0 -44%;
opacity: 0;
}
}
@-ms-keyframes uil-ripple {
0% {
width: 0;
height: 0;
opacity: 0;
margin: 0 0 0 0;
}
33% {
width: 44%;
height: 44%;
margin: -22% 0 0 -22%;
opacity: 1;
}
100% {
width: 88%;
height: 88%;
margin: -44% 0 0 -44%;
opacity: 0;
}
}
@-moz-keyframes uil-ripple {
0% {
width: 0;
height: 0;
opacity: 0;
margin: 0 0 0 0;
}
33% {
width: 44%;
height: 44%;
margin: -22% 0 0 -22%;
opacity: 1;
}
100% {
width: 88%;
height: 88%;
margin: -44% 0 0 -44%;
opacity: 0;
}
}
@-webkit-keyframes uil-ripple {
0% {
width: 0;
height: 0;
opacity: 0;
margin: 0 0 0 0;
}
33% {
width: 44%;
height: 44%;
margin: -22% 0 0 -22%;
opacity: 1;
}
100% {
width: 88%;
height: 88%;
margin: -44% 0 0 -44%;
opacity: 0;
}
}
@-o-keyframes uil-ripple {
0% {
width: 0;
height: 0;
opacity: 0;
margin: 0 0 0 0;
}
33% {
width: 44%;
height: 44%;
margin: -22% 0 0 -22%;
opacity: 1;
}
100% {
width: 88%;
height: 88%;
margin: -44% 0 0 -44%;
opacity: 0;
}
}
@keyframes uil-ripple {
0% {
width: 0;
height: 0;
opacity: 0;
margin: 0 0 0 0;
}
33% {
width: 44%;
height: 44%;
margin: -22% 0 0 -22%;
opacity: 1;
}
100% {
width: 88%;
height: 88%;
margin: -44% 0 0 -44%;
opacity: 0;
}
}
.uil-ripple-css {
background: none;
position: relative;
width: 200px;
height: 200px;
}
.uil-ripple-css div {
position: absolute;
top: 50%;
left: 50%;
margin: 0;
width: 0;
height: 0;
opacity: 0;
border-radius: 50%;
border-width: 12px;
border-style: solid;
-ms-animation: uil-ripple 2s ease-out infinite;
-moz-animation: uil-ripple 2s ease-out infinite;
-webkit-animation: uil-ripple 2s ease-out infinite;
-o-animation: uil-ripple 2s ease-out infinite;
animation: uil-ripple 2s ease-out infinite;
}
.uil-ripple-css div:nth-of-type(1) {
border-color: #afafb7;
}
.uil-ripple-css div:nth-of-type(2) {
border-color: #5cffd6;
-ms-animation-delay: 1s;
-moz-animation-delay: 1s;
-webkit-animation-delay: 1s;
-o-animation-delay: 1s;
animation-delay: 1s;
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

41
app/fonts/fonts.css Normal file
View File

@ -0,0 +1,41 @@
@font-face {
font-family: 'Material Icons';
font-style: normal;
font-weight: 400;
src: url(Material_Icons-normal-400.woff) format('woff');
}
@font-face {
font-family: 'Ubuntu';
font-style: normal;
font-weight: 300;
src: url(Ubuntu-normal-300.woff) format('woff');
}
@font-face {
font-family: 'Ubuntu';
font-style: normal;
font-weight: 400;
src: url(Ubuntu-normal-400.woff) format('woff');
}
@font-face {
font-family: 'Ubuntu';
font-style: normal;
font-weight: 500;
src: url(Ubuntu-normal-500.woff) format('woff');
}
@font-face {
font-family: 'Ubuntu';
font-style: normal;
font-weight: 700;
src: url(Ubuntu-normal-700.woff) format('woff');
}
@font-face {
font-family: 'Ubuntu Condensed';
font-style: normal;
font-weight: 400;
src: url(Ubuntu_Condensed-normal-400.woff) format('woff');
}

BIN
app/img/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

115
app/index.html Normal file
View File

@ -0,0 +1,115 @@
<!DOCTYPE html>
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
<html>
<head>
<!--
Customize this policy to fit your own app's needs. For more guidance, see:
https://github.com/apache/cordova-plugin-whitelist/blob/master/README.md#content-security-policy
Some notes:
* gap: is required only on iOS (when using UIWebView) and is needed for JS->native communication
* https://ssl.gstatic.com is required only on Android and is needed for TalkBack to function properly
* Disables use of inline scripts in order to mitigate risk of XSS vulnerabilities. To change this:
* Enable inline JS: add 'unsafe-inline' to default-src
-->
<meta http-equiv="Content-Security-Policy" content="default-src 'self' data: gap: https://ssl.gstatic.com 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src *">
<meta name="format-detection" content="telephone=no">
<meta name="msapplication-tap-highlight" content="no">
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width">
<link rel="stylesheet"
href="http://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700"
type="text/css">
<link href='https://fonts.googleapis.com/css?family=Ubuntu+Condensed'
rel='stylesheet' type='text/css'>
<link href="https://fonts.googleapis.com/icon?family=Material+Icons"
rel="stylesheet">
<!-- endbuild -->
<!-- build:css -->
<link rel="stylesheet" href="fonts/fonts.css">
<link href="css/mui.custom.css" rel="stylesheet" type="text/css"/>
<link href="css/app.css" rel="stylesheet" type="text/css"/>
<link href="css/ripple.css" rel="stylesheet" type="text/css"/>
<link href="css/progress.css" rel="stylesheet" type="text/css"/>
<title>Sensor Toy</title>
</head>
<body>
<div style="margin: 0.5rem 0 1rem 0;">
<div class="progress" id='ripple' style="display: none;">
<div class="indeterminate"></div>
</div>
</div>
<div id='app' class="mui-container">
<div class="mui-panel">
<div class="mui-row">
<div class="mui-col-xs-6">
<button class="mui-btn mui-btn--primary" id="scan">Scan</button>
<button class="mui-btn mui-btn--danger" id="stop" style="display: none;">Stop</button>
</div>
<div class="mui-col-xs-6">
<button class="mui-btn mui-btn--accent" id="longScan">Long</button>
</div>
</div>
</div>
<table class="mui-table mui--text-white" id="results">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>RSI</th>
</tr>
</thead>
<tbody id="tbody">
</tbody>
</table>
</div>
<div id="frames" class="mui-container">
</div>
<script type="text/javascript" src="cordova.js"></script>
<script type="text/javascript" src="libs/jquery/dist/jquery.js"></script>
<script type="text/javascript" src="libs/mui/packages/cdn/js/mui.js"></script>
<script type="text/javascript" src="libs/bluebird/js/browser/bluebird.js"></script>
<script type="text/javascript" src="js/standards/bluetooth_company_identifiers.js"></script>
<script type="text/javascript" src="js/standards/capability.js"></script>
<script type="text/javascript" src="js/standards/battery.js"></script>
<script type="text/javascript" src="js/standards/button.js"></script>
<script type="text/javascript" src="js/mandecoder.js"></script>
<script type="text/javascript" src="js/device/CC2650/cc2650_accelerometer.js"></script>
<script type="text/javascript" src="js/device/CC2650/cc2650_barometer.js"></script>
<script type="text/javascript" src="js/device/CC2650/cc2650_luxometer.js"></script>
<script type="text/javascript" src="js/device/CC2650/cc2650_thermopile.js"></script>
<script type="text/javascript" src="js/device/CC2650/cc2650_humidity.js"></script>
<script type="text/javascript" src="js/index.js"></script>
</body>
</html>

View File

@ -0,0 +1,172 @@
/**
*
* User: Martin Donnelly
* Date: 2016-05-20
* Time: 10:13
*
*/
/* global CAPABILITY, inheritsFrom */
/* global ble */
/* jshint browser: true , devel: true*/
var CC2650_ACCEL = function(deviceId) {
'use strict';
this.name = 'Accelerometer';
this.deviceID = deviceId;
this.capabilityID = 'F000AA80-0451-4000-B000-000000000000';
this.serviceDef = {
service: 'F000AA80-0451-4000-B000-000000000000',
data: 'F000AA81-0451-4000-B000-000000000000', // Read/notify 3 bytes X : Y : Z
notification: 'F0002902-0451-4000-B000-000000000000',
configuration: 'F000AA82-0451-4000-B000-000000000000', // Read/write 1 byte
period: 'F000AA83-0451-4000-B000-000000000000' // Read/write 1 byte Period = [Input*10]ms
};
this.frames = {};
this.$id = {};
this.$result = {};
this.sensorMpu9250GyroConvert = function(data) {
return data / (65536 / 500);
};
this.sensorMpu9250AccConvert = function(data) {
// Change /2 to match accel range...i.e. 16 g would be /16
return data / (32768 / 2);
};
this.onAccelerometerData = function(data) {
// Console.log(data);
var message;
var a = new Int16Array(data);
//0 gyro x
//1 gyro y
//2 gyro z
//3 accel x
//4 accel y
//5 accel z
//6 mag x
//7 mag y
//8 mag z
// TODO get a template to line this up
// TODO round or format numbers for better display
message = 'Gyro <br/>' +
'X: ' + this.sensorMpu9250GyroConvert(a[0]).toFixed(5) + '<br/>' +
'Y: ' + this.sensorMpu9250GyroConvert(a[1]) + '<br/>' +
'Z: ' + this.sensorMpu9250GyroConvert(a[2]) + '<br/>' +
'Accel <br/>' +
'X: ' + this.sensorMpu9250AccConvert(a[3]) + '<br/>' +
'Y: ' + this.sensorMpu9250AccConvert(a[4]) + '<br/>' +
'Z: ' + this.sensorMpu9250AccConvert(a[5]) + '<br/>' +
'Mag <br/>' +
'X: ' + a[3] + '<br/>' +
'Y: ' + a[4] + '<br/>' +
'Z: ' + a[5] + '<br/>' ;
this.state = message;
this.$result[this.frames.gyroID + '-x'].text(this.sensorMpu9250GyroConvert(a[0]).toFixed(5));
this.$result[this.frames.gyroID + '-y'].text(this.sensorMpu9250GyroConvert(a[1]).toFixed(5));
this.$result[this.frames.gyroID + '-z'].text(this.sensorMpu9250GyroConvert(a[2]).toFixed(5));
this.$result[this.frames.accelID + '-x'].text(this.sensorMpu9250AccConvert(a[3]).toFixed(5));
this.$result[this.frames.accelID + '-y'].text(this.sensorMpu9250AccConvert(a[4]).toFixed(5));
this.$result[this.frames.accelID + '-z'].text(this.sensorMpu9250AccConvert(a[5]).toFixed(5));
this.$result[this.frames.magID + '-x'].text(a[3]);
this.$result[this.frames.magID + '-y'].text(a[4]);
this.$result[this.frames.magID + '-z'].text(a[5]);
// Console.log(this.state);
};
this.startService = function() {
'use strict';
if (this.deviceID !== null) {
console.log('Starting CC2650 Accelerometer Service on ', this.deviceID);
console.log(this.serviceDef);
//Ble.startNotification(this.deviceID, , this.onButtonData.bind(this), this.onError);
ble.startNotification(this.deviceID, this.serviceDef.service, this.serviceDef.data, this.onAccelerometerData.bind(this), this.onError);
// Turn accelerometer on
var configData = new Uint16Array(1);
// Turn on gyro, accel, and mag, 2G range, Disable wake on motion
configData[0] = 0x007F;
ble.write(this.deviceID, this.serviceDef.service, this.serviceDef.configuration, configData.buffer,
function() { console.log('Started accelerometer.'); }, this.onError);
var periodData = new Uint8Array(1);
periodData[0] = 0x0A;
// Ble.write(deviceId, accelerometer.service, accelerometer.period, periodData.buffer,
// function() { console.log("Configured accelerometer period."); },app.onError);
this.setInternalID();
this.insertFrame('gyro');
this.insertFrame('accel');
this.insertFrame('mag');
}
};
this.animateGraph = function() {
// Nothing to animate yet
return -1;
};
this.insertFrame = function(mode) {
var frame;
var title;
var modeID = mode + 'ID';
this.frames[modeID] = this.frameID + '-' + mode;
var titles = {gyro: 'Gyroscope', accel: 'Accelerometer', mag: 'Magnetometer'};
console.log('FrameID: ' , this.frames[modeID]);
title = [titles[mode], ' - ', this.deviceID].join(' ');
frame = $('<div />', {
class: 'mui-panel', id: this.frames[modeID]
});
$('<div />', { class: 'mui-row'}).append($('<div />', { class: 'mui-col-xs-12 mui--text-title mui-ellipsis-2', text: title})).appendTo(frame);
$('#frames').append(frame);
this.$id[modeID] = $('#' + this.frames[modeID]);
// Call the parent displayForm first...
var row = $('<div />', {class: 'mui-row'});
$('<div />', { class: 'mui-col-xs-3 mui--text-accent', text: 'X'}).appendTo(row);
$('<div />', { class: 'mui-col-xs-3 mui--text-accent', text: 'Y'}).appendTo(row);
$('<div />', { class: 'mui-col-xs-3 mui--text-accent', text: 'Z'}).appendTo(row);
this.$id[modeID].append(row);
row = $('<div />', {class: 'mui-row'});
$('<div />', { class: 'mui-col-xs-3 mui--text-white', text: '--', id: this.frames[modeID] + '-x' }).appendTo(row);
$('<div />', { class: 'mui-col-xs-3 mui--text-white', text: '--', id: this.frames[modeID] + '-y'}).appendTo(row);
$('<div />', { class: 'mui-col-xs-3 mui--text-white', text: '--', id: this.frames[modeID] + '-z'}).appendTo(row);
this.$id[modeID].append(row);
this.$result[this.frames[modeID] + '-x'] = $('#' + this.frames[modeID] + '-x');
this.$result[this.frames[modeID] + '-y'] = $('#' + this.frames[modeID] + '-y');
this.$result[this.frames[modeID] + '-z'] = $('#' + this.frames[modeID] + '-z');
};
};
inheritsFrom(CC2650_ACCEL, CAPABILITY);

View File

@ -0,0 +1,138 @@
/**
*
* User: Martin Donnelly
* Date: 2016-05-20
* Time: 10:13
*
*/
/* global CAPABILITY, inheritsFrom */
/* global ble */
/* jshint browser: true , devel: true*/
var CC2650_BAR = function(deviceId) {
'use strict';
this.name = 'Barometer';
this.deviceID = deviceId;
this.capabilityID = 'F000AA40-0451-4000-B000-000000000000';
this.serviceDef = {
service: 'F000AA40-0451-4000-B000-000000000000',
data: 'F000AA41-0451-4000-B000-000000000000',
notification: 'F0002902-0451-4000-B000-000000000000',
configuration: 'F000AA42-0451-4000-B000-000000000000',
period: 'F000AA43-0451-4000-B000-000000000000'
};
this.data = {temp: [], pressure: []};
this.$result = {temp: null, pressure: null};
this.startService = function() {
'use strict';
if (this.deviceID !== null) {
console.log('Starting CC2650 Barometer Service on ', this.deviceID);
console.log(this.serviceDef);
this.insertFrame();
ble.startNotification(this.deviceID, this.serviceDef.service, this.serviceDef.data, this.onBarometerData.bind(this), this.onError);
//Turn on barometer
var barometerConfig = new Uint8Array(1);
barometerConfig[0] = 0x01;
ble.write(this.deviceID, this.serviceDef.service, this.serviceDef.configuration, barometerConfig.buffer,
function() { console.log('Started barometer.'); },this.onError);
}
};
this.sensorBarometerConvert = function(data) {
return (data / 100);
};
this.onBarometerData = function(data) {
var pStr;
var tStr;
// Console.log(data);
var message;
var a = new Uint8Array(data);
//0-2 Temp
//3-5 Pressure
var temp, pressure;
temp = this.sensorBarometerConvert(a[0] | (a[1] << 8) | (a[2] << 16));
pressure = this.sensorBarometerConvert(a[3] | (a[4] << 8) | (a[5] << 16));
tStr = temp + '°C';
pStr = pressure + 'hPa';
message = 'Temperature <br/>' + tStr +
'Pressure <br/>' + pStr ;
// This.data.temp = this.storeData(parseInt(temp), this.data.temp);
// this.data.pressure = this.storeData(parseInt(pressure), this.data.pressure);
this.data.temp = this.storeData(temp, this.data.temp);
this.data.pressure = this.storeData(pressure, this.data.pressure);
this.$result.temp.text(tStr);
this.$result.pressure.text(pStr);
this.state = message;
// Console.log('Barometer:', this.state);
};
this.animateGraph = function() {
this.simpleGraph(this.data.temp, 'temp');
this.simpleGraph(this.data.pressure, 'pressure');
};
this.insertFrame = function() {
var self = this;
var blankChart;
// Call the parent displayForm first...
this.superClass_.insertFrame.call(self);
var temp = this.frameID + '-t';
var pressure = this.frameID + '-p';
var row = $('<div />', {class: 'mui-row'});
$('<div />', { class: 'mui-col-xs-3 mui--text-accent', text: 'Temp:'}).appendTo(row);
$('<div />', { class: 'mui-col-xs-3 mui--text-white', id: temp}).appendTo(row);
$('<div />', { class: 'mui-col-xs-3 mui--text-accent', text: 'Pressure:'}).appendTo(row);
$('<div />', { class: 'mui-col-xs-3 mui--text-white', id: pressure}).appendTo(row);
this.$id.append(row);
var tabBody = $('<ul>',{class: 'mui-tabs__bar mui-tabs__bar--justified'});
$('<li>',{class: 'mui--is-active'}).append($('<a>',{text:'Temperature','data-mui-toggle': 'tab', 'data-mui-controls': (temp + '-pane')})).appendTo(tabBody);
$('<li>').append($('<a>',{text:'Pressure','data-mui-toggle': 'tab', 'data-mui-controls': (pressure + '-pane')})).appendTo(tabBody);
this.$id.append(tabBody);
blankChart = this.generateBlankGraph('temp');
// this.$id.append(blankChart);
this.$id.append($('<div>',{'class':'mui-tabs__pane mui--is-active',id:(temp + '-pane')}).append(blankChart));
blankChart = this.generateBlankGraph('pressure');
// this.$id.append(blankChart);
this.$id.append($('<div>',{'class':'mui-tabs__pane',id:(pressure + '-pane')}).append(blankChart));
this.$result.temp = $('#' + temp);
this.$result.pressure = $('#' + pressure);
};
};
inheritsFrom(CC2650_BAR, CAPABILITY);

View File

@ -0,0 +1,145 @@
/**
*
* User: Martin Donnelly
* Date: 2016-05-20
* Time: 10:13
*
*/
/* global CAPABILITY, inheritsFrom */
/* global ble */
/* jshint browser: true , devel: true*/
var CC2650_HUM = function(deviceId) {
'use strict';
this.name = 'Humidity';
this.deviceID = deviceId;
this.capabilityID = 'F000AA20-0451-4000-B000-000000000000';
this.serviceDef = {
service: 'F000AA20-0451-4000-B000-000000000000',
data: 'F000AA21-0451-4000-B000-000000000000',
notification: 'F0002902-0451-4000-B000-000000000000',
configuration: 'F000AA22-0451-4000-B000-000000000000',
period: 'F000AA23-0451-4000-B000-000000000000'
};
this.data = {temp: [], humidity: []};
this.$result = {temp: null, humidity: null};
this.startService = function() {
'use strict';
if (this.deviceID !== null) {
console.log('Starting CC2650 Humidity Service on ', this.deviceID);
console.log(this.serviceDef);
this.insertFrame();
ble.startNotification(this.deviceID,
this.serviceDef.service,
this.serviceDef.data,
this.onHumidityData.bind(this),
this.onError);
//Turn on barometer
var humidityConfig = new Uint8Array(1);
humidityConfig[0] = 0x01;
ble.write(this.deviceID,
this.serviceDef.service,
this.serviceDef.configuration,
humidityConfig.buffer,
function() { console.log('Started Humidity.'); },
this.onError);
}
};
this.onHumidityData = function(data) {
var hStr;
var tStr;
// Console.log(data);
var message;
var raw = new Uint16Array(data);
//-- calculate temperature [°C]
var temp = (raw[0] / 65536) * 165 - 40;
//-- calculate relative humidity [%RH]
var hum = (raw[1] / 65536) * 100;
tStr = temp.toFixed(2) + '°C';
hStr = hum.toFixed(2) + '%RH';
message = 'Temperature <br/>' + tStr + 'Humidity <br/>' + hStr;
// This.data.temp = this.storeData(parseInt(temp), this.data.temp);
// this.data.humidity = this.storeData(parseInt(hum), this.data.humidity);
this.data.temp = this.storeData(temp, this.data.temp);
this.data.humidity = this.storeData(hum, this.data.humidity);
this.$result.temp.text(tStr);
this.$result.humidity.text(hStr);
this.state = message;
// Console.log('Barometer:', this.state);
};
this.animateGraph = function() {
this.simpleGraph(this.data.temp, 'temp');
this.simpleGraph(this.data.humidity, 'humidity');
};
this.insertFrame = function() {
var self = this;
var blankChart;
// Call the parent displayForm first...
this.superClass_.insertFrame.call(self);
var temp = this.frameID + '-t';
var humidity = this.frameID + '-h';
var row = $('<div />', {class: 'mui-row'});
$('<div />',
{class: 'mui-col-xs-3 mui--text-accent', text: 'Temp:'}).appendTo(row);
$('<div />',
{class: 'mui-col-xs-3 mui--text-white', id: temp}).appendTo(row);
$('<div />',
{
class: 'mui-col-xs-3 mui--text-accent',
text: 'Humidity:'
}).appendTo(row);
$('<div />',
{class: 'mui-col-xs-3 mui--text-white', id: humidity}).appendTo(row);
this.$id.append(row);
var tabBody = $('<ul>',{class: 'mui-tabs__bar mui-tabs__bar--justified'});
$('<li>',{class: 'mui--is-active'}).append($('<a>',{text: 'Temperature','data-mui-toggle': 'tab', 'data-mui-controls': (temp + '-pane')})).appendTo(tabBody);
$('<li>').append($('<a>',{text: 'Humidity','data-mui-toggle': 'tab', 'data-mui-controls': (humidity + '-pane')})).appendTo(tabBody);
this.$id.append(tabBody);
blankChart = this.generateBlankGraph('temp');
// This.$id.append(blankChart);
this.$id.append($('<div>',{class: 'mui-tabs__pane mui--is-active',id: (temp + '-pane')}).append(blankChart));
blankChart = this.generateBlankGraph('humidity');
this.$id.append($('<div>',{class: 'mui-tabs__pane',id: (humidity + '-pane')}).append(blankChart));
// this.$id.append(blankChart);
this.$result.temp = $('#' + temp);
this.$result.humidity = $('#' + humidity);
};
};
inheritsFrom(CC2650_HUM, CAPABILITY);

View File

@ -0,0 +1,116 @@
/**
*
* User: Martin Donnelly
* Date: 2016-05-20
* Time: 10:13
*
*/
/* global CAPABILITY, inheritsFrom */
/* global ble */
/* jshint browser: true , devel: true*/
var CC2650_LUX = function(deviceId) {
'use strict';
this.name = 'Luxometer';
this.deviceID = deviceId;
this.capabilityID = 'F000AA70-0451-4000-B000-000000000000';
this.serviceDef = {
service: 'F000AA70-0451-4000-B000-000000000000',
data: 'F000AA71-0451-4000-B000-000000000000',
notification: 'F0002902-0451-4000-B000-000000000000',
configuration: 'F000AA72-0451-4000-B000-000000000000',
period: 'F000AA73-0451-4000-B000-000000000000'
};
this.$result = {temp: null, pressure: null};
this.data = [];
this.startService = function() {
'use strict';
if (this.deviceID !== null) {
console.log('Starting CC2650 Luxometer Service on ', this.deviceID);
console.log(this.serviceDef);
this.insertFrame();
ble.startNotification(this.deviceID,
this.serviceDef.service,
this.serviceDef.data,
this.onLuxData.bind(this),
this.onError);
//Turn on luxometer
var luxConfig = new Uint8Array(1);
luxConfig[0] = 0x01;
ble.write(this.deviceID,
this.serviceDef.service,
this.serviceDef.configuration,
luxConfig.buffer,
function() { console.log('Started luxometer.'); },
this.onError);
}
};
this.onLuxData = function(data) {
var m, e, lux;
// Console.log(data);
var raw = new Uint16Array(data);
m = raw & 0x0FFF;
e = (raw & 0xF000) >> 12;
lux = m * (0.01 * Math.pow(2.0, e));
this.state = [lux.toFixed(2), 'lux'].join(' ');
//This.storeData(parseInt(lux));
this.storeData(lux);
this.$result.text(this.state);
// Console.log('Luxometer:', this.state);
};
this.animateGraph = function() {
this.simpleGraph(this.data, '');
};
this.insertFrame = function() {
var self = this;
// Call the parent displayForm first...
this.superClass_.insertFrame.call(self);
var lux = this.frameID + '-l';
var row = $('<div />', {class: 'mui-row'});
$('<div />',
{class: 'mui-col-xs-4 mui--text-accent', text: 'Luminosity:'}).appendTo(
row);
$('<div />',
{class: 'mui-col-xs-8 mui--text-white', id: lux}).appendTo(row);
this.$id.append(row);
var blankChart = this.generateBlankGraph();
this.$id.append(blankChart);
this.$result = $('#' + lux);
//Window.requestAnimFrame(this.animateFrame.bind(this));
};
};
inheritsFrom(CC2650_LUX, CAPABILITY);

View File

@ -0,0 +1,152 @@
/**
*
* User: Martin Donnelly
* Date: 2016-05-20
* Time: 10:13
*
*/
/* global CAPABILITY, inheritsFrom */
/* global ble */
/* jshint browser: true , devel: true*/
var CC2650_TMP = function(deviceId) {
'use strict';
this.name = 'Thermopile';
this.deviceID = deviceId;
this.capabilityID = 'F000AA00-0451-4000-B000-000000000000';
this.serviceDef = {
service: 'F000AA00-0451-4000-B000-000000000000',
data: 'F000AA01-0451-4000-B000-000000000000',
notification: 'F0002902-0451-4000-B000-000000000000',
configuration: 'F000AA02-0451-4000-B000-000000000000',
period: 'F000AA03-0451-4000-B000-000000000000'
};
this.data = {temp: [], ambient: []};
this.$result = {temp: null, ambient: null};
this.startService = function() {
'use strict';
if (this.deviceID !== null) {
console.log('Starting CC2650 Thermopile Service on ', this.deviceID);
console.log(this.serviceDef);
this.insertFrame();
ble.startNotification(this.deviceID,
this.serviceDef.service,
this.serviceDef.data,
this.onThermData.bind(this),
this.onError);
//Turn on thermopile
var tmpConfig = new Uint8Array(1);
tmpConfig[0] = 0x01;
ble.write(this.deviceID,
this.serviceDef.service,
this.serviceDef.configuration,
tmpConfig.buffer,
function() { console.log('Started Thermopile.'); },
this.onError);
}
};
this.onThermData = function(data) {
var ambTemp;
var objTemp;
var scale_lsb = 0.03125;
var raw = new Uint16Array(data);
var it = raw[0] >> 2;
objTemp = it * scale_lsb;
it = raw[1] >> 2;
ambTemp = it * scale_lsb;
var tStr = objTemp.toFixed(2) + '°C';
var aStr = ambTemp.toFixed(2) + '°C';
this.state = ['Temp: ', tStr, ', Ambient:', aStr].join(' ');
this.data.temp = this.storeData(objTemp, this.data.temp);
this.data.ambient = this.storeData(ambTemp, this.data.ambient);
this.$result.temp.text(tStr);
this.$result.ambient.text(aStr);
// Console.log('Thermopile:', this.state);
};
this.animateGraph = function() {
this.simpleGraph(this.data.temp, 'temp');
this.simpleGraph(this.data.ambient, 'ambient');
};
this.insertFrame = function() {
var blankChart;
var self = this;
// Console.log('Overloading...');
// Call the parent displayForm first...
this.superClass_.insertFrame.call(self);
var temp = this.frameID + '-t';
var amb = this.frameID + '-a';
var row = $('<div />', {class: 'mui-row'});
$('<div />',
{class: 'mui-col-xs-3 mui--text-accent', text: 'Temp:'}).appendTo(row);
$('<div />',
{class: 'mui-col-xs-3 mui--text-white', id: temp}).appendTo(row);
$('<div />',
{
class: 'mui-col-xs-3 mui--text-accent',
text: 'Ambient:'
}).appendTo(row);
$('<div />',
{class: 'mui-col-xs-3 mui--text-white', id: amb}).appendTo(row);
this.$id.append(row);
var tabBody = $('<ul>',{class: 'mui-tabs__bar mui-tabs__bar--justified'});
$('<li>',{class: 'mui--is-active'}).append($('<a>',{text:'Temperature','data-mui-toggle': 'tab', 'data-mui-controls': (temp + '-pane')})).appendTo(tabBody);
$('<li>').append($('<a>',{text:'Ambient','data-mui-toggle': 'tab', 'data-mui-controls': (amb + '-pane')})).appendTo(tabBody);
this.$id.append(tabBody);
/*
<div class="mui-tabs__pane mui--is-active" id="pane-justified-1">Pane-1</div>
<div class="mui-tabs__pane" id="pane-justified-2">Pane-2</div>
*/
blankChart = this.generateBlankGraph('temp');
//this.$id.append(blankChart);
this.$id.append($('<div>',{'class':'mui-tabs__pane mui--is-active',id:(temp + '-pane')}).append(blankChart));
blankChart = this.generateBlankGraph('ambient');
//this.$id.append(blankChart);
this.$id.append($('<div>',{'class':'mui-tabs__pane',id:(amb + '-pane')}).append(blankChart));
this.$result.temp = $('#' + temp);
this.$result.ambient = $('#' + amb);
};
};
inheritsFrom(CC2650_TMP, CAPABILITY);

471
app/js/index.js Normal file
View File

@ -0,0 +1,471 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/* global BATTERY, BUTTON */
var app = {
stop: false,
log: {},
activeServices: [],
list: {},
manufactureDecoder: new MANUFACTUREDECODER(),
// Application Constructor
initialize: function() {
this.bindEvents();
},
arrayBufferToIntArray: function(buffer) {
var result;
if (buffer) {
var typedArray = new Uint8Array(buffer);
result = [];
for (var i = 0; i < typedArray.length; i++) {
result[i] = typedArray[i];
}
}
return result;
},
parseAdvertisingData: function(bytes) {
var length, type, data, i = 0, advertisementData = {};
while (length !== 0) {
length = bytes[i] & 0xFF;
i++;
type = bytes[i] & 0xFF;
i++;
data = bytes.slice(i, i + length - 1); // Length includes type byte, but not length byte
i += length - 2; // Move to end of data
i++;
advertisementData[type] = data;
}
return advertisementData;
},
handle255: function(buffer) {
'use strict';
var company;
var cid;
var manID;
var bin = buffer;
var decoded = {};
console.log('Block255', bin);
var manSpec = bin.map(function(i) {
return i.toString(16) + ' ';
});
console.log('manSpec: ', manSpec);
manID = app.manufactureDecoder.getManID(bin);
console.log('ManID:', manID);
cid = '0x' + manID;
company = bt_company_ids.find(cid);
switch (manID) {
case '004c':
decoded = app.manufactureDecoder.decodeIbeacon(bin);
decoded.company = company;
return decoded;
break;
case '1235':
decoded = app.manufactureDecoder.decodeSiliconLabsSensorPuck(bin);
decoded.company = company;
return decoded;
break;
case '0060':
decoded = app.manufactureDecoder.decodeSansible(bin);
decoded.company = company;
return decoded;
break;
default:
console.log('Unknown manID: ', manID);
}
return {company:company};
},
makeHexBuffer : function(buffer) {
'use strict';
var hexBuffer = buffer.map(function(i) {
return ('00' + i.toString(16)).slice(-2) + ',';
});
return hexBuffer;
},
makeChars : function(buffer) {
'use strict';
var output = buffer.map(function(i) {
return String.fromCharCode(i);
});
return output;
},
doScan: function(mode) {
'use strict';
$('#ripple').show();
if (mode !== 2) {
$('#tbody').empty();
}
var otherData = null;
var msgText = '';
ble.startScan([], function(device) {
var parsed;
var hexBuffer;
var advertBuffer;
var newTR;
var newId;
var _device = device;
otherData = null;
msgText = '';
console.log(JSON.stringify(device));
newId = device.id.replace(/:/g, '');
console.log(newId);
this.list[newId] = device.id;
newTR = $('<tr id="' + newId + '" class="clickRow">');
newTR.append($('<td>').text(device.id));
if (device.hasOwnProperty('advertising')) {
advertBuffer = app.arrayBufferToIntArray(device.advertising);
hexBuffer = app.makeHexBuffer(advertBuffer);
parsed = app.parseAdvertisingData(advertBuffer);
//Console.log(parsed);
if (parsed.hasOwnProperty('9')) {
var name = app.makeChars(parsed['9']);
_device.name = name.join('');
console.log('Name: ', name.join(''));
}
if (parsed.hasOwnProperty('255')) {
otherData = app.handle255(parsed['255']);
console.log(otherData);
_device.otherData = otherData;
}
_device.advertBuffer = advertBuffer;
_device.hexBuffer = hexBuffer;
_device.parsed = parsed;
}
if (typeof otherData !== 'undefined' && otherData !== null) {
if (otherData.hasOwnProperty('msg')) {
msgText = ' - ' + otherData.msg;
}
}
if (device.hasOwnProperty('name')) {
newTR.append($('<td>').text(device.name + msgText));
} else {
newTR.append($('<td>').text('*** Unknown' + msgText));
}
newTR.append($('<td>').text(device.rssi));
if ($('tr#' + newId).length > 0) {
$('tr#' + newId).replaceWith(newTR);
} else {
$('#tbody').append(newTR);
}
//$('#output').append(JSON.stringify(device) + '<br/>');
app.log[newId] = _device;
console.log(JSON.stringify(_device));
}.bind(this), function(e) {
'use strict';
console.error(e);
});
var _t = [5000,60000,200][mode];
setTimeout(ble.stopScan,
_t,
function() { console.log('Scan complete');
if (mode === 1) {
app.saveLog();
$('#ripple').hide();
}
if (mode === 2) {
if (!app.stop) {
setTimeout(function() {
app.doScan(2);
}.bind(this), 200);
} else {
app.saveLog();
$('#ripple').hide();
}
}
},
function() { console.log('stopScan failed');
$('#ripple').hide();
});
},
writeFile: function(fileEntry, dataObj) {
// Create a FileWriter object for our FileEntry (log.txt).
fileEntry.createWriter(function(fileWriter) {
fileWriter.onwriteend = function() {
console.log('Successful file write...');
// ReadFile(fileEntry);
};
fileWriter.onerror = function(e) {
console.error('Failed file write: ' + e.toString());
};
// If data object is not passed in,
// create a new Blob instead.
if (!dataObj) {
dataObj = new Blob(['some file data'], { type: 'text/plain' });
}
fileWriter.write(dataObj);
});
},
saveLog: function() {
'use strict';
var dt = new Date().toISOString().replace(/:|-/g,'').replace(/(\.\w+)/g,'');
var payload = JSON.stringify(app.log);
var filename = 'sensortoy-' + dt + '.json';
// Var dataObj = new Blob(payload, { type: 'text/plain' });
window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, function(fs) {
console.log('file system open: ' + fs.name);
fs.root.getFile(filename, { create: true, exclusive: false }, function(fileEntry) {
console.log('fileEntry is file?' + fileEntry.isFile.toString());
// FileEntry.name == 'someFile.txt'
// fileEntry.fullPath == '/someFile.txt'
console.log('Path: ', fileEntry.fullPath);
app.writeFile(fileEntry, payload);
app.log = [];
}, app.onError);
}, app.onError);
},
forceStop: function() {
'use strict';
app.stop = true;
$('#scan').show();
$('#stop').hide();
},
// Bind Event Listeners
//
// Bind any events that are required on startup. Common events are:
// 'load', 'deviceready', 'offline', and 'online'.
bindEvents: function() {
var self = this;
document.addEventListener('deviceready', this.onDeviceReady, false);
$('#scan').on('click', function() {
'use strict';
this.stop = false;
this.doScan(2);
$('#scan').hide();
$('#stop').show();
}.bind(this));
$('#stop').on('click', function() {
'use strict';
app.forceStop();
}.bind(this));
$('#longScan').on('click', function() {
'use strict';
this.doScan(1);
}.bind(this));
$('#tbody').on('click', 'tr', function() {
'use strict';
var tID = $(this).context.id;
var id = self.list[tID];
console.log(tID, id);
app.forceStop();
self.connect(id);
});
}, // Deviceready Event Handler
//
// The scope of 'this' is the event. In order to call the 'receivedEvent'
// function, we must explicitly call 'app.receivedEvent(...);'
onDeviceReady: function() {
}, serviceDiscovery: function(services) {
'use strict';
console.log(services);
}, sensorMpu9250GyroConvert: function(data) {
return data / (65536 / 500);
},
sensorMpu9250AccConvert: function(data) {
// Change /2 to match accel range...i.e. 16 g would be /16
return data / (32768 / 2);
},
doAnimate: function() {
'use strict';
// Console.log('Animate..');
for (var t = 0; t < app.activeServices.length;t++) {
app.activeServices[t].animateGraph();
}
window.requestAnimFrame(app.doAnimate.bind(this));
}
, connect: function(deviceId) {
$('#results').slideUp();
console.log('Connect to ', deviceId);
var onConnect = function(a) {
var services = [];
'use strict';
console.log('A:', a);
services = a.services;
for (var t = 0; t < services.length; t++) {
var ident = services[t].toUpperCase();
switch (ident) {
case '180F':
var batteryStat = new BATTERY(deviceId);
batteryStat.startService();
// batteryStat.readBatteryState();
app.activeServices.push(batteryStat);
break;
case 'FFE0':
var buttonState = new BUTTON(deviceId);
buttonState.startService();
app.activeServices.push(buttonState);
break;
case 'F000AA80-0451-4000-B000-000000000000':
var cc2650_accel = new CC2650_ACCEL(deviceId);
cc2650_accel.startService();
app.activeServices.push(cc2650_accel);
break;
case 'F000AA40-0451-4000-B000-000000000000':
var cc2650_bar = new CC2650_BAR(deviceId);
cc2650_bar.startService();
app.activeServices.push(cc2650_bar);
break;
case 'F000AA70-0451-4000-B000-000000000000':
var cc2650_lux = new CC2650_LUX(deviceId);
cc2650_lux.startService();
app.activeServices.push(cc2650_lux);
break;
case 'F000AA00-0451-4000-B000-000000000000':
var cc2650_tmp = new CC2650_TMP(deviceId);
cc2650_tmp.startService();
app.activeServices.push(cc2650_tmp);
break;
case 'F000AA20-0451-4000-B000-000000000000':
var cc2650_hum = new CC2650_HUM(deviceId);
cc2650_hum.startService();
app.activeServices.push(cc2650_hum);
break;
default:
console.error('Unknown service: ', ident);
}
}
// Starting animation..
window.requestAnimFrame(app.doAnimate.bind(this));
};
ble.connect(deviceId, onConnect, function(e) {
'use strict';
console.error(e);
});
}, onError: function(reason) {
console.error('ERROR: ' + reason); // Real apps should use notification.alert
}, updateGyro: function(g) {
'use strict';
}
};
window.requestAnimFrame = (function() {
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000 / 60);
};
})();
app.initialize();

69
app/js/mandecoder.js Normal file
View File

@ -0,0 +1,69 @@
/**
*
* User: Martin Donnelly
* Date: 2016-05-24
* Time: 14:21
*
*/
var MANUFACTUREDECODER = function() {
'use strict';
this.getManID = function(data) {
return ('0000' + ((data[1] << 8) | data[0]).toString(16)).slice(-4).toUpperCase();
},
this.decodeIbeacon = function(data) {
// Not decoding anything yet.
// https://support.kontakt.io/hc/en-gb/articles/201492492-iBeacon-advertising-packet-structure
var bin = data;
var obj = { msg: '(iBeacon)'};
// obj.manID = ('0000' + ((bin[1] << 8) | bin[0]).toString(16)).slice(-4);
obj.manID = this.getManID(bin);
var uuid = [];
uuid.push(bin[4].toString(16) + bin[5].toString(16) + bin[6].toString(16) + bin[7].toString(16)) ;
uuid.push(bin[8].toString(16) + bin[9].toString(16)) ;
uuid.push(bin[10].toString(16) + bin[11].toString(16)) ;
uuid.push(bin[12].toString(16) + bin[13].toString(16)) ;
uuid.push(bin[14].toString(16) + bin[15].toString(16) + bin[16].toString(16) + bin[17].toString(16) + bin[18].toString(16) + bin[19].toString(16)) ;
obj.uuid = uuid.join('-');
return obj;
};
this.decodeSiliconLabsSensorPuck = function(data) {
var bin = data;
var obj = {};
//obj.manID = ('0000' + ((bin[1] << 8) | bin[0]).toString(16)).slice(-4);
obj.manID = this.getManID(bin);
obj.a = (bin[3] << 8) | bin[2];
obj.b = (bin[5] << 8) | bin[4];
obj.humidity = (bin[7] << 8) | bin[6];
obj.temp = (bin[9] << 8) | bin[8];
obj.c = (bin[11] << 8) | bin[10];
obj.d = (bin[13] << 8) | bin[12];
obj.msg = 'Humidity: ' + (obj.humidity / 10) + ', temp: ' + (obj.temp / 10);
return obj;
};
this.decodeSansible = function(data) {
var bin = data;
var obj = {};
// obj.manID = ('0000' + ((bin[1] << 8) | bin[0]).toString(16)).slice(-4);
obj.manID = this.getManID(bin);
obj.p1 = ((bin[2] << 16) | bin[3] << 8 | bin[4])
obj.p2 = ((bin[5] << 16) | bin[6] << 8 | bin[7])
obj.msg = 'Left: ' + (obj.p1 / 100) + ' hPa, Right: ' + (obj.p2 / 100) + ' hPa';
return obj;
};
};

View File

@ -0,0 +1,61 @@
/**
*
* User: Martin Donnelly
* Date: 2016-05-20
* Time: 10:13
*
*/
/* global CAPABILITY */
/* global ble */
/* jshint browser: true , devel: true*/
var BATTERY = function() {
this.name = 'Battery';
this.capabilityID = '180F';
this.serviceDef = {
service: '180F', level: '2A19'
};
this.onBatteryLevelChange = function(data) {
console.log(data);
var a = new Uint8Array(data);
this.state = a[0];
console.log('onBatteryLevelChange', this.state);
};
this.readBatteryState = function() {
console.log('readBatteryState');
ble.read(this.deviceID,
this.serviceDef.service,
this.serviceDef.level,
this.onReadBatteryLevel.bind(this),
this.onError);
};
this.onReadBatteryLevel = function(data) {
console.log(data);
var a = new Uint8Array(data);
this.state = a[0];
console.log('onReadBatteryLevel', this.state);
};
this.startService = function() {
'use strict';
if (this.deviceID !== null) {
console.log('Starting Battery Service on ', this.deviceID);
console.log(this.serviceDef);
this.insertFrame();
ble.startNotification(this.deviceID,
this.serviceDef.service,
this.serviceDef.level,
this.onBatteryLevelChange.bind(this),
this.onError);
}
};
};
inheritsFrom(BATTERY, CAPABILITY);

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,96 @@
/**
*
* User: Martin Donnelly
* Date: 2016-05-20
* Time: 10:13
*
*/
/* global CAPABILITY, inheritsFrom */
/* global ble */
/* jshint browser: true , devel: true*/
var BUTTON = function(deviceId) {
'use strict';
this.name = 'Button';
this.deviceID = deviceId;
this.capabilityID = 'FFE0';
this.serviceDef = {
service: 'FFE0',
data: 'FFE1' // Bit 2: side key, Bit 1- right key, Bit 0 left key
};
this.buttonMatrix = {
LEFT_BUTTON: 1, // 0001
RIGHT_BUTTON: 2, // 0010
REED_SWITCH: 4 // 0100
};
this.onButtonData = function(data) {
console.log('+ onButtonData');
console.log(data);
var state = new Uint8Array(data);
var message = '';
if (state === 0) {
message = 'No buttons are pressed.';
}
if (state & this.buttonMatrix.LEFT_BUTTON) {
message += 'Left button is pressed. ';
}
if (state & this.buttonMatrix.RIGHT_BUTTON) {
message += 'Right button is pressed.';
}
if (state & this.buttonMatrix.REED_SWITCH) {
message += 'Reed switch is activated.<br/>';
}
this.state = message;
if (this.$result !== null)
{
this.$result.text(this.state);
}
console.log('ButtonState: ', this.state);
console.log('- onButtonData');
};
this.startService = function() {
'use strict';
if (this.deviceID !== null) {
console.log('Starting Button Service on ', this.deviceID);
console.log(this.serviceDef);
ble.startNotification(this.deviceID, this.serviceDef.service, this.serviceDef.data, this.onButtonData.bind(this), this.onError);
}
this.insertFrame();
};
this.insertFrame = function() {
var self = this;
console.log('Overloading...');
// Call the parent displayForm first...
this.superClass_.insertFrame.call(self);
var detail = this.frameID + '-d';
var row = $('<div />', {class: 'mui-row'});
$('<div />', { class: 'mui-col-xs-4 mui--text-accent', text: 'Status:'}).appendTo(row);
$('<div />', { class: 'mui-col-xs-8 mui--text-white', id: detail}).appendTo(row);
this.$id.append(row);
this.$result = $('#'+detail);
};
};
inheritsFrom(BUTTON, CAPABILITY);

View File

@ -0,0 +1,249 @@
/**
*
* User: Martin Donnelly
* Date: 2016-05-20
* Time: 10:32
*
*/
'use strict';
var CAPABILITY = function(p) {
this.name = '-CAPABILITY-';
this.capabilityID = p.capabilityID || null;
this.state = '';
this.serviceDef = p.service || null;
this.internalID = null;
this.frameID = null;
this.$id = null;
this.deviceID = null;
this.data = [];
this.ctx = null;
this.first = false;
};
CAPABILITY.prototype.setInternalID = function() {
this.internalID = (Math.floor(Math.random() * Number.MAX_SAFE_INTEGER) + 1).toString(36);
this.frameID = 'f-' + this.internalID;
};
CAPABILITY.prototype.setDeviceID = function(dID) {
this.deviceID = dID;
};
CAPABILITY.prototype.getDeviceID = function() {
return this.deviceID ;
};
CAPABILITY.prototype.insertFrame = function() {
this.setInternalID();
console.log('FrameID: ' , this.frameID);
var title = [this.name, ' - ', this.deviceID].join(' ');
var frame = $('<div />', {
class: 'mui-panel',
id: this.frameID});
$('<div />', { class: 'mui-row'}).append($('<div />', { class: 'mui-col-xs-12 mui--text-title', text: title})).appendTo(frame);
$('#frames').append(frame);
this.$id = $('#' + this.frameID);
};
CAPABILITY.prototype.inherits = function(a, b) {
var c = function() {};
c.prototype = b.prototype;
a.superClass_ = b.prototype;
a.prototype = new c;
a.prototype.constructor = a;
};
CAPABILITY.prototype.onError = function(e) {
console.error(e);
};
CAPABILITY.prototype.storeData = function(data, alt) {
if (!this.first) {
this.first = true;
return [];
}
var target = alt || this.data;
if (target.length === 99) {
target = target.slice(1);
}
target.push(data);
if (alt) {
return target;
} else {
this.data = target;
}
};
CAPABILITY.prototype.startGraph = function(id) {
var c;
c = id[0].getContext('2d');
this.ctx = c;
this.ctx.fillStyle = '#ffffff';
this.ctx.fillRect(0,0,300,150);
};
CAPABILITY.prototype.generateBlankGraph = function(subID) {
var _subID = subID || '';
var xmlns = 'http://www.w3.org/2000/svg';
var svgID = this.frameID + _subID + '-svg';
var text1ID = this.frameID + _subID + '-txt1';
var lineID = this.frameID + _subID + '-line';
var svg = document.createElementNS(xmlns,'svg');
svg.setAttributeNS(xmlns,'id',svgID);
svg.setAttributeNS(xmlns,'width',300);
svg.setAttributeNS(xmlns,'height',150);
svg.setAttributeNS(xmlns,'fill', 'blue');
var line = document.createElementNS(xmlns,'line');
line.setAttributeNS(null,'x1','46');
line.setAttributeNS(null,'y1','12');
line.setAttributeNS(null,'x2','280');
line.setAttributeNS(null,'y2', '12');
line.setAttributeNS(null,'style','stroke:#bad649;stroke-width:2;');
svg.appendChild(line);
line = document.createElementNS(xmlns,'line');
line.setAttributeNS(null,'x1','46');
line.setAttributeNS(null,'y1','136');
line.setAttributeNS(null,'x2','280');
line.setAttributeNS(null,'y2', '136');
line.setAttributeNS(null,'style','stroke:#bad649;stroke-width:2;');
svg.appendChild(line);
var text = document.createElementNS(xmlns,'text');
text.setAttributeNS(null,'id',text1ID);
text.setAttributeNS(null,'x','36');
text.setAttributeNS(null,'y','15');
text.setAttributeNS(null,'text-anchor', 'end');
text.setAttributeNS(null,'style','font-family:"Ubuntu Condensed",sans-serif;font-size:12;fill: #bad649;text-align:right;');
text.textContent = '000';
svg.appendChild(text);
text = document.createElementNS(xmlns,'text');
text.setAttributeNS(null,'id','text2');
text.setAttributeNS(null,'x','36');
text.setAttributeNS(null,'y','140');
text.setAttributeNS(null,'text-anchor', 'end');
text.setAttributeNS(null,'style','font-family:"Ubuntu Condensed",sans-serif;font-size:12;fill: #bad649;text-align:right;');
text.textContent = '0';
svg.appendChild(text);
var polyline = document.createElementNS(xmlns,'polyline');
polyline.setAttributeNS(null,'id',lineID);
polyline.setAttributeNS(null,'fill','none');
polyline.setAttributeNS(null,'stroke','#e5f7fd');
//#e5f7fd
// old #B5C7FF
polyline.setAttributeNS(null,'text-anchor', 'end');
polyline.setAttributeNS(null,'stroke-width','2');
svg.appendChild(polyline);
return svg;
};
CAPABILITY.prototype.animateGraph = function() {
//This.simpleGraph(this.data);
};
CAPABILITY.prototype.simpleGraph = function(data, subID) {
var _subID;
var _data;
var text1ID;
var lineID;
_data = data || this.data;
_subID = subID || '';
lineID = [this.frameID , _subID , '-line'].join('');
text1ID = [this.frameID , _subID , '-txt1'].join('');
if (_data.length > 0) {
var ceiling = _data.reduce(function(p, v) {
return (p > v ? p : v);
});
/* Var floor = _data.reduce(function(p, v) {
return (p < v ? p : v);
});
*/
var calcArray = [];
var ceilingLimit = Math.floor(ceiling / 10) * 10;
if (ceilingLimit < ceiling) {
ceilingLimit = Math.floor((ceiling + (ceiling * 0.25)) / 10) * 10;
}
if (ceilingLimit < 30) {
ceilingLimit = 30;
}
var scale = 124 / ceilingLimit;
// Var xstep = (280 - 46) / 100;
var xstep = 2.34;
var startX = 46 + (100 - _data.length) * xstep;
for (var x = 0;x < _data.length;x++) {
calcArray.push((startX + (x * xstep)).toFixed(2) + ',' + (136 - ((_data[x]) * scale)).toFixed(2));
}
var elm = document.getElementById(lineID);
elm.setAttribute('points',calcArray.join(' '));
elm = document.getElementById(text1ID);
elm.textContent = ceilingLimit;
}
};
var inheritsFrom = function(a, b) {
var c = function() {};
c.prototype = b.prototype;
a.superClass_ = b.prototype;
a.prototype = new c;
a.prototype.constructor = a;
a.prototype.superClass_ = b.prototype;
};

55
app/js/test.js Normal file
View File

@ -0,0 +1,55 @@
/**
* Created by martin on 2016-05-20.
*/
var DEVICEBASE = function(p) {
this.controller = p.controller || null;
this.settings = p.settings || {};
this.data = {};
this.standards = {
button: {
service: 'FFE0',
data: 'FFE1' // Bit 2: side key, Bit 1- right key, Bit 0 left key
},
heartRate: {
service: '180d',
measurement: '2a37'
}
};
this.custom = {};
this.capabilities = [];
this.deviceID = null;
this.inherits = function(a, b) {
var c = function() {};
c.prototype = b.prototype;
a.superClass_ = b.prototype;
a.prototype = new c;
a.prototype.constructor = a;
};
this.register = function(c) {
c.registerPage(this);
};
this.bytesToString = function(buffer) {
return String.fromCharCode.apply(null, new Uint8Array(buffer));
};
// ASCII only
this.stringToBytes = function(string) {
var array = new Uint8Array(string.length);
for (var i = 0, l = string.length; i < l; i++) {
array[i] = string.charCodeAt(i);
}
return array.buffer;
};
this.register(this.controller);
};

View File

@ -0,0 +1,44 @@
{
"name": "bluebird",
"version": "3.4.0",
"homepage": "https://github.com/petkaantonov/bluebird",
"authors": [
"Petka Antonov <petka_antonov@hotmail.com>"
],
"description": "Bluebird is a full featured promise library with unmatched performance.",
"main": "js/browser/bluebird.js",
"license": "MIT",
"ignore": [
"**/.*",
"benchmark",
"bower_components",
"./browser",
"node_modules",
"test"
],
"keywords": [
"promise",
"performance",
"promises",
"promises-a",
"promises-aplus",
"async",
"await",
"deferred",
"deferreds",
"future",
"flow control",
"dsl",
"fluent interface"
],
"_release": "3.4.0",
"_resolution": {
"type": "version",
"tag": "v3.4.0",
"commit": "3aeb023d26df1e738e37a162a8f81b36afe1c6b9"
},
"_source": "https://github.com/petkaantonov/bluebird.git",
"_target": "^3.4.0",
"_originalSource": "bluebird",
"_direct": true
}

1
app/libs/bluebird/API.md Normal file
View File

@ -0,0 +1 @@
[http://bluebirdjs.com/docs/api-reference.html](http://bluebirdjs.com/docs/api-reference.html)

View File

@ -0,0 +1,9 @@
# Questions and issues
Please see [The Support Page](http://bluebirdjs.com/docs/support.html)
The [github issue tracker](https://github.com/petkaantonov/bluebird/issues) is **_only_** for bug reports and feature requests.
# Contributing to the library
Contributions are welcome and appreciated. See the [Contribution Page](http://bluebirdjs.com/docs/contribute.html) on bluebirdjs.com

21
app/libs/bluebird/LICENSE Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2013-2015 Petka Antonov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -0,0 +1,51 @@
<a href="http://promisesaplus.com/">
<img src="http://promisesaplus.com/assets/logo-small.png" alt="Promises/A+ logo"
title="Promises/A+ 1.1 compliant" align="right" />
</a>
[![Build Status](https://travis-ci.org/petkaantonov/bluebird.svg?branch=master)](https://travis-ci.org/petkaantonov/bluebird)
[![coverage-98%](http://img.shields.io/badge/coverage-98%-brightgreen.svg?style=flat)](http://petkaantonov.github.io/bluebird/coverage/debug/index.html)
**Got a question?** Join us on [stackoverflow](http://stackoverflow.com/questions/tagged/bluebird), the [mailing list](https://groups.google.com/forum/#!forum/bluebird-js) or chat on [IRC](https://webchat.freenode.net/?channels=#promises)
# Introduction
Bluebird is a fully featured promise library with focus on innovative features and performance
See the [**bluebird website**](http://bluebirdjs.com/docs/getting-started.html) for further documentation, references and instructions. See the [**API reference**](http://bluebirdjs.com/docs/api-reference.html) here.
For bluebird 2.x documentation and files, see the [2.x tree](https://github.com/petkaantonov/bluebird/tree/2.x).
# Questions and issues
The [github issue tracker](https://github.com/petkaantonov/bluebird/issues) is **_only_** for bug reports and feature requests. Anything else, such as questions for help in using the library, should be posted in [StackOverflow](http://stackoverflow.com/questions/tagged/bluebird) under tags `promise` and `bluebird`.
## Thanks
Thanks to BrowserStack for providing us with a free account which lets us support old browsers like IE8.
# License
The MIT License (MIT)
Copyright (c) 2013-2016 Petka Antonov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

37
app/libs/bluebird/bench Executable file
View File

@ -0,0 +1,37 @@
#!/usr/bin/env bash
./build --release --no-debug
benchmark=$1
nodepath=${2:-node}
shift 2;
cwd=${PWD}
trap 'cd "$cwd"' EXIT
if [ "$benchmark" = "doxbee" ]; then
cd "$cwd/benchmark"
npm install
echo "Doxbee sequential"
$nodepath performance.js --n 10000 --t 1 ./doxbee-sequential/*.js --harmony "$@"
exit 0
elif [ "$benchmark" = "doxbee-errors" ]; then
cd "$cwd/benchmark"
npm install
echo "Doxbee sequential with 10% errors"
$nodepath performance.js --n 10000 --t 1 --e 0.1 ./doxbee-sequential-errors/*.js --harmony "$@"
exit 0
elif [ "$benchmark" = "parallel" ]; then
cd "$cwd/benchmark"
npm install
echo "Madeup parallel"
$nodepath performance.js --n 10000 --t 1 --p 25 ./madeup-parallel/*.js --harmony "$@"
exit 0
elif [ "$benchmark" = "analysis" ]; then
cd "$cwd/benchmark"
npm install
echo "analysis"
$nodepath performance.js --n 10000 --t 1 ./analysis/*.js --harmony "$@"
exit 0
else
echo "Invalid benchmark name $benchmark"
exit -1
fi

View File

@ -0,0 +1,34 @@
{
"name": "bluebird",
"version": "3.4.0",
"homepage": "https://github.com/petkaantonov/bluebird",
"authors": [
"Petka Antonov <petka_antonov@hotmail.com>"
],
"description": "Bluebird is a full featured promise library with unmatched performance.",
"main": "js/browser/bluebird.js",
"license": "MIT",
"ignore": [
"**/.*",
"benchmark",
"bower_components",
"./browser",
"node_modules",
"test"
],
"keywords": [
"promise",
"performance",
"promises",
"promises-a",
"promises-aplus",
"async",
"await",
"deferred",
"deferreds",
"future",
"flow control",
"dsl",
"fluent interface"
]
}

2
app/libs/bluebird/build Executable file
View File

@ -0,0 +1,2 @@
#!/usr/bin/env bash
node tools/build.js "$@"

View File

@ -0,0 +1 @@
[http://bluebirdjs.com/docs/changelog.html](http://bluebirdjs.com/docs/changelog.html)

View File

@ -0,0 +1 @@
[http://bluebirdjs.com/docs/deprecated-apis.html](http://bluebirdjs.com/docs/deprecated-apis.html)

View File

@ -0,0 +1,8 @@
source "https://rubygems.org"
gem "jekyll"
gem "jekyll-redirect-from"
gem "sanitize", '4.0.1'
gem "redcarpet"
gem "pygments.rb"
gem 'wdm', '>= 0.1.0' if Gem.win_platform?

View File

@ -0,0 +1,5 @@
Requires ruby and [jekyll](http://jekyllrb.com/). See the gem file for dependencies.
Change directory to `bluebird/docs` and run `jekyll serve`. The docs will be hosted under `/docs` directory in relation to the web root. Typically something like `http://localhost:4000/docs/`

View File

@ -0,0 +1,35 @@
name: bluebird
description: Bluebird is a fully featured JavaScript promises library with unmatched performance.
url: "http://bluebirdjs.com"
baseurl: ""
title: bluebird
timezone: Helsinki/Finland
highlighter: pygments
exclude:
- Gemfile
- Gemfile.lock
- helpers.rb
defaults:
- scope:
path: docs
type: pages
values:
layout: page
markdown: redcarpet
redcarpet:
extensions:
- fenced_code_blocks
version: 3.4.0
gems:
- jekyll-redirect-from
destination: ../gh-pages/
keep_files:
- .git
- .gitignore
- logo.png
- CNAME
- coverage
safe: false
encoding: utf-8
host: 0.0.0.0

View File

@ -0,0 +1,9 @@
---
layout: default
---
<div class="post">
<article class="post-content">
{{ content }}
</article>
</div>

View File

@ -0,0 +1,136 @@
<!DOCTYPE html>
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!-->
<html class="no-js">
<!--<![endif]-->
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href='http://fonts.googleapis.com/css?family=Raleway:400,300,200,700,500,100,800,600,900' rel='stylesheet' type='text/css'>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css" />
<title>{% if page.title %}{{ page.title }} | {{ site.title }}{% else %}{{ site.title }}{% endif %}</title>
<meta name="description" content="{% if page.excerpt %}{{ page.excerpt | strip_html | strip_newlines | truncate: 160 }}{% else %}{{ site.description }}{% endif %}">
<link href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css" rel="stylesheet">
<link rel="stylesheet" href="{{ "/css/mono-blue.css" | prepend: site.baseurl }}" type='text/css' />
<link rel="stylesheet" href="{{ "/css/hover-min.css" | prepend: site.baseurl }}" media="all">
<link rel="stylesheet" href="{{ "/css/main.css" | prepend: site.baseurl }}">
<link rel="canonical" href="{{ page.url | replace:'index.html','' | prepend: site.baseurl | prepend: site.url }}">
<link rel="icon" href="{{ "/img/favicon.png" | prepend: site.baseurl }}" type="image/png" />
</head>
<body>
<nav class="navbar" role="navigation">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a href="/" class="title">
<img src="{{ "/img/logo.png" | prepend: site.baseurl }}" class="hidden-xs" />
<span class="tagline">bluebird</span>
</a>
</div>
<div id="navbar" class="navbar-collapse navbar-bluebird navbar-right collapse">
<ul class="nav navbar-nav bb-nav">
{% if page.path == 'docs/support.md' %}
{% assign name = 'support' %}
{% elsif page.path == 'docs/install.md' %}
{% assign name = 'install' %}
{% else %}
{% assign name = 'docs' %}
{% endif %}
<li class="{% if name == 'docs' %}active{% endif %}"><a href="{{ "/docs/getting-started.html" | prepend: site.baseurl }}">Docs</a></li>
<li class="{% if name == 'support' %}active{% endif %}"><a href="{{ "/docs/support.html" | prepend: site.baseurl }}">Support</a></li>
<li class="{% if name == 'install' %}active{% endif %}"><a href="{{ "/docs/install.html" | prepend: site.baseurl }}">Install</a></li>
<li><a href="https://github.com/petkaantonov/bluebird/">Github</a></li>
</ul>
</div><!--/.navbar-collapse -->
</div>
</nav>
<div class="container">
<div class="row">
<div class="col-sm-3">
<ul class="nav left-nav">
<li><a href="{{ "/docs/getting-started.html" | prepend: site.baseurl }}">Getting Started</a></li>
<li><a href="{{ "/docs/features.html" | prepend: site.baseurl }}">Features</a></li>
<li><a href="{{ "/docs/changelog.html" | prepend: site.baseurl }}">Changelog</a></li>
<li><a href="{{ "/docs/api-reference.html" | prepend: site.baseurl }}"><strong>API Reference</strong></a></li>
<li><a href="{{ "/docs/new-in-bluebird-3.html" | prepend: site.baseurl }}"><strong>New in 3.0</strong></a></li>
<li><a href="{{ "/docs/warning-explanations.html" | prepend: site.baseurl }}">Warning Explanations</a></li>
<li><a href="{{ "/docs/error-explanations.html" | prepend: site.baseurl }}">Error Explanations</a></li>
<li><a href="{{ "/docs/contribute.html" | prepend: site.baseurl }}">Contribute</a></li>
<li><a href="{{ "/docs/benchmarks.html" | prepend: site.baseurl }}">Benchmarks</a></li>
<li><a href="{{ "/docs/deprecated-apis.html" | prepend: site.baseurl }}">Deprecated APIs</a></li>
<li><a href="{{ "/docs/download-api-reference.html" | prepend: site.baseurl }}">Download API Reference</a></li>
<li><hr></li>
<li>Why?
<ul class="nav nav-child">
<li><a href="{{ "/docs/why-promises.html" | prepend: site.baseurl }}">Why Promises?</a></li>
<li><a href="{{ "/docs/why-bluebird.html" | prepend: site.baseurl }}">Why bluebird?</a></li>
<li><a href="{{ "/docs/why-performance.html" | prepend: site.baseurl }}">Why Performance?</a></li>
<li><a href="{{ "/docs/what-about-generators.html" | prepend: site.baseurl }}">What About Generators?</a></li>
</ul>
</li>
<li><hr></li>
<li>
Tutorials
<ul class="nav nav-child">
<li><a href="{{ "/docs/async-dialogs.html" | prepend: site.baseurl }}">Async Dialogs</a></li>
</ul>
</li>
<li><hr></li>
<li>
Guides
<ul class="nav nav-child">
<li><a href="{{ "/docs/beginners-guide.html" | prepend: site.baseurl }}">Beginner's Guide</a></li>
<li><a href="{{ "/docs/anti-patterns.html" | prepend: site.baseurl }}">Anti-patterns</a></li>
<li><a href="{{ "/docs/working-with-callbacks.html" | prepend: site.baseurl }}">Working with Callbacks</a></li>
<li><a href="{{ "/docs/coming-from-other-languages.html" | prepend: site.baseurl }}">Coming from Other Languages</a></li>
<li><a href="{{ "/docs/coming-from-other-libraries.html" | prepend: site.baseurl }}">Coming from Other Libraries</a></li>
</ul>
</li>
</ul>
</div>
<div class="col-sm-9">
{% if page.path %}
<div class="post-info">
<a href="{{ page.path | prepend: "https://github.com/petkaantonov/bluebird/edit/master/docs/" }}">
<i class="fa fa-edit"></i>
Edit on Github</a>
<br>
<i>Updated {{ page.path | file_date | date_to_string }}</i>
</div>
<div class="clearfix"></div>
{% endif %}
{{ content }}
</div>
</div>
</div>
<footer></footer>
<script src="https://code.jquery.com/jquery-2.1.3.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js"></script>
<script src="//cdn.jsdelivr.net/bluebird/{{ site.version }}/bluebird.js"></script>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-46253177-1', 'auto');
ga('send', 'pageview');
</script>
</body>
</html>

View File

@ -0,0 +1,14 @@
---
layout: default
---
<div class="post">
<header class="post-header">
<h1 class="post-title">{{ page.title }}</h1>
</header>
<article class="post-content">
{{ content }}
</article>
</div>

View File

@ -0,0 +1,19 @@
module Jekyll
module MyFilters
def file_date(input)
File.mtime(input)
end
def check_active(page_path, link_type)
if (link_type == "support" and page_path =~ /support/) or
(link_type == "install" and page_path =~ /install/) or
(link_type == "docs")
"active"
else
""
end
end
end
end
Liquid::Template.register_filter(Jekyll::MyFilters)

View File

@ -0,0 +1,55 @@
require "redcarpet"
require "pygments"
require_relative "../helpers"
class Redcarpet::Render::HTML
def header(title, level)
anchor = Helpers.clean(title)
return <<-eos
<h#{level}>
<a class="header-anchor"
name="#{anchor}"
aria-hidden="true"
href="##{anchor}"><i class="fa fa-link"></i></a>
#{title}
</h#{level}>
eos
end
# Hacks to get markdown working inside html blocks ....
def block_html(html)
html.gsub(/<markdown>([\d\D]+?)<\/markdown>/) {|_|
extensions = {fenced_code_blocks: true}
renderer = Redcarpet::Markdown.new(WithHighlights, extensions)
renderer.render(Regexp.last_match[1])
}
end
def link(link, title, content)
if link == "unfinished-article"
return <<-eos
<div class="info-box">
This article is partially or completely unfinished.
You are welcome to create <a href="https://github.com/petkaantonov/bluebird/edit/master/docs/docs/#{content}.md">pull requests</a>
to help completing this article.
</div>
eos
elsif link == "."
if content =~ /#\d+/
url = "https://github.com/petkaantonov/bluebird/issues/" + content[1..-1]
else
url = "/docs/api/" + Helpers.clean(content) + ".html"
end
return "<a href='#{url}'><code>#{content}</code></a>"
else
return "<a href='#{link}' title='#{title}'>#{content}</a>"
end
end
end
class WithHighlights < Redcarpet::Render::HTML
def block_code(code, language)
Pygments.highlight(code, :lexer => language)
end
end

View File

@ -0,0 +1,181 @@
body {
font-size: 16px;
}
body > .container {
padding-bottom: 50px;
}
.navbar-bluebird {
font-family: 'Raleway', sans-serif;
font-size: 18px;
margin-top: 50px;
text-transform:uppercase;
}
.nav {
font-family: 'Raleway', sans-serif;
}
.nav.bb-nav > li > a{
transition: all 0.3s ease-in-out;
border-bottom: 1px solid rgba(0,0,0,0);
}
.nav.bb-nav > li > a:focus, .nav.bb-nav > li > a:hover,
.nav.bb-nav > li.active > a {
background-color:initial;
border-bottom:1px solid #BE7306;
}
.nav a {
color: #BE7306;
}
.nav.left-nav li a {
padding: 2px 0px;
font-size: 14px;
font-family: 'Raleway', sans-serif;
}
.nav.left-nav li a:focus, .nav.left-nav li a:hover {
background: none;
text-decoration: underline;
}
.navbar-bluebird li {
text-align:center;
}
h1,h2,h3,h4,h5,h6{
font-family: 'Raleway', sans-serif;
/*color:#5275B4;*/
color: #1B4288;
}
.api-code-section h2, code, pre {
font-family: 'Consolas', 'Lucida Console', 'Courier New', 'monospace'
}
body {
margin-top:10px;
}
.icon-bar {
background-color:#5275B4;
}
.promises-showroom .arrow {
text-align: center;
margin-top: 100px;
color:#EC9313;
}
pre{
background-color: initial;
border: none;
margin:0px;
padding:0px;
}
.promises-showroom .hljs{
border-radius: 6px;
padding-left: 30px;
padding-bottom: 15px;
}
.take-action{
position:relative;
min-height:300px;
}
.take-action .cloud-bg{
width: 100%;
height: 300px;
border: none;
}
.take-action .action-buttons{
position:absolute;
top:50%;
text-align:center;
width:100%;
}
.btn-bb {
font-family: 'Raleway', sans-serif;
color: white;
}
.btn-beak{
background-color: #EC9313;
border-bottom: 4px solid #BE7306;
}
.btn-wings {
background-color: #265584;
border-bottom: 4px solid #1A3A59;
}
.btn-bb:hover{
color:white;
}
.undo-margin{
margin-left:-15px;
}
.main-line{
font-family: 'Raleway', sans-serif;
margin-bottom: 59px;
margin-top: -74px;
font-size: 20px;
color:white;
}
.tagline {
font-family: 'Raleway', sans-serif;
font-size:26px;
color:#5275B4;
font-weight:200;
}
.nav-child {
margin-left:20px;
}
.title:hover{
text-decoration:none;
}
.header-anchor {
opacity: 0;
-webkit-transition: opacity 0.2s ease-in-out 0.1s;
-moz-transition: opacity 0.2s ease-in-out 0.1s;
-ms-transition: opacity 0.2s ease-in-out 0.1s;
outline: none;
}
.header-anchor:active, .header-anchor:focus {
outline: none;
}
h1:hover .header-anchor,
h2:hover .header-anchor,
h3:hover .header-anchor,
h4:hover .header-anchor,
h5:hover .header-anchor,
h6:hover .header-anchor {
opacity: 1;
}
.api-reference-menu {
-moz-column-count: 2;
-webkit-column-count: 2;
columns: 2;
margin-bottom: 50px;
}
.post-info {
float: right;
}
.info-box {
margin-top: 20px;
color: #8a6d3b;
background-color: #fcf8e3;
border-color: #faebcc;
padding: 15px;
margin-bottom: 20px;
border: 1px dashed #8a6d3b;
border-radius: 4px;
}

View File

@ -0,0 +1,80 @@
.highlight {
display: block;
overflow-x: auto;
padding: 0.5em;
margin: 15px 0px;
background: #eaeef3;
-webkit-text-size-adjust: none;
color: #00193a;
}
code {
color: #00193a;
background: #eaeef3;
}
a code {
color: #337AB7;
}
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #738191; font-style: italic } /* Comment */
.highlight .err { border: 1px solid #FF0000 } /* Error */
.highlight .k { color: #4c81c9; font-weight: bold } /* Keyword */
.highlight .o { color: #666666 } /* Operator */
.highlight .cm { color: #738191; font-style: italic } /* Comment.Multiline */
.highlight .cp { color: #BC7A00 } /* Comment.Preproc */
.highlight .c1 { color: #738191; font-style: italic } /* Comment.Single */
.highlight .cs { color: #738191; font-style: italic } /* Comment.Special */
.highlight .gd { color: #A00000 } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .gr { color: #FF0000 } /* Generic.Error */
.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */
.highlight .gi { color: #00A000 } /* Generic.Inserted */
.highlight .go { color: #808080 } /* Generic.Output */
.highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
.highlight .gt { color: #0040D0 } /* Generic.Traceback */
.highlight .kc { color: #4c81c9; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #4c81c9; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #4c81c9; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #4c81c9 } /* Keyword.Pseudo */
.highlight .kr { color: #4c81c9; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #B00040 } /* Keyword.Type */
.highlight .m { color: #666666 } /* Literal.Number */
.highlight .s { color: #BE7306;} /* Literal.String */
.highlight .na { color: #7D9029 } /* Name.Attribute */
.highlight .nb { color: #4c81c9 } /* Name.Builtin */
.highlight .nc { color: #0000FF; font-weight: bold } /* Name.Class */
.highlight .no { color: #880000 } /* Name.Constant */
.highlight .nd { color: #AA22FF } /* Name.Decorator */
.highlight .ni { color: #999999; font-weight: bold } /* Name.Entity */
.highlight .ne { color: #D2413A; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0000FF } /* Name.Function */
.highlight .nl { color: #A0A000 } /* Name.Label */
.highlight .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */
.highlight .nt { color: #4c81c9; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #19177C } /* Name.Variable */
.highlight .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mf { color: #666666 } /* Literal.Number.Float */
.highlight .mh { color: #666666 } /* Literal.Number.Hex */
.highlight .mi { color: #666666 } /* Literal.Number.Integer */
.highlight .mo { color: #666666 } /* Literal.Number.Oct */
.highlight .sb { color: #BE7306 } /* Literal.String.Backtick */
.highlight .sc { color: #BE7306 } /* Literal.String.Char */
.highlight .sd { color: #BE7306; font-style: italic } /* Literal.String.Doc */
.highlight .s2 { color: #BE7306 } /* Literal.String.Double */
.highlight .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */
.highlight .sh { color: #BE7306 } /* Literal.String.Heredoc */
.highlight .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */
.highlight .sx { color: #BE7306 } /* Literal.String.Other */
.highlight .sr { color: #BB6688 } /* Literal.String.Regex */
.highlight .s1 { color: #BE7306 } /* Literal.String.Single */
.highlight .ss { color: #19177C } /* Literal.String.Symbol */
.highlight .bp { color: #4c81c9 } /* Name.Builtin.Pseudo */
.highlight .vc { color: #19177C } /* Name.Variable.Class */
.highlight .vg { color: #19177C } /* Name.Variable.Global */
.highlight .vi { color: #19177C } /* Name.Variable.Instance */
.highlight .il { color: #666666 } /* Literal.Number.Integer.Long */

View File

@ -0,0 +1,172 @@
---
id: anti-patterns
title: Anti-patterns
---
This page will contain common promise anti-patterns that are exercised in the wild.
- [The explicit construction anti-pattern](#the-explicit-construction-anti-pattern)
- [The `.then(success, fail)` anti-pattern](#the-.then)
##The Explicit Construction Anti-Pattern
This is the most common anti-pattern. It is easy to fall into this when you don't really understand promises and think of them as glorified event emitters or callback utility. It's also sometimes called the promise constructor anti-pattern. Let's recap: promises are about making asynchronous code retain most of the lost properties of synchronous code such as flat indentation and one exception channel. This pattern is also called the deferred anti-pattern.
In the explicit construction anti-pattern, promise objects are created for no reason, complicating code.
First example is creating deferred object when you already have a promise or thenable:
```js
//Code copyright by Twisternha http://stackoverflow.com/a/19486699/995876 CC BY-SA 2.5
myApp.factory('Configurations', function (Restangular, MotorRestangular, $q) {
var getConfigurations = function () {
var deferred = $q.defer();
MotorRestangular.all('Motors').getList().then(function (Motors) {
//Group by Config
var g = _.groupBy(Motors, 'configuration');
//Map values
var mapped = _.map(g, function (m) {
return {
id: m[0].configuration,
configuration: m[0].configuration,
sizes: _.map(m, function (a) {
return a.sizeMm
})
}
});
deferred.resolve(mapped);
});
return deferred.promise;
};
return {
config: getConfigurations()
}
});
```
This superfluous wrapping is also dangerous, any kind of errors and rejections are swallowed and not propagated to the caller of this function.
Instead of using the Deferred anti-pattern, the code should simply return the promise it already has and propagate values using `return`:
```js
myApp.factory('Configurations', function (Restangular, MotorRestangular, $q) {
var getConfigurations = function () {
//Just return the promise we already have!
return MotorRestangular.all('Motors').getList().then(function (Motors) {
//Group by Cofig
var g = _.groupBy(Motors, 'configuration');
//Return the mapped array as the value of this promise
return _.map(g, function (m) {
return {
id: m[0].configuration,
configuration: m[0].configuration,
sizes: _.map(m, function (a) {
return a.sizeMm
})
}
});
});
};
return {
config: getConfigurations()
}
});
```
Not only is the code shorter but more importantly, if there is any error it will propagate properly to the final consumer.
Second example is creating a function that does nothing but manually wrap a callback API and doing a poor job at that:
```js
function applicationFunction(arg1) {
return new Promise(function(resolve, reject){ //Or Q.defer() in Q
libraryFunction(arg1, function (err, value) {
if (err) {
reject(err);
} else {
resolve(value);
}
});
}
```
This is reinventing the square wheel because any callback API wrapping can and should be done immediately using the promise library's promisification methods:
```js
var applicationFunction = Promise.promisify(libraryFunction);
```
The generic promisification is likely to be faster because it can use internals directly but also handles edge cases like `libraryFunction` throwing synchronously or using multiple success values.
**So when should deferred be used?**
Well simply, when you have to.
You might have to use a deferred object when wrapping a callback API that doesn't follow the standard convention. Like `setTimeout`:
```js
//setTimeout that returns a promise
function delay(ms) {
var deferred = Promise.defer(); // warning, defer is deprecated, use the promise constructor
setTimeout(function(){
deferred.fulfill();
}, ms);
return deferred.promise;
}
```
Such wrappers should be rare, if they're common for the reason that the promise library cannot generically promisify them, you should file an issue.
If you cannot do static promisification (promisify and promisifyAll perform too slowly to use at runtime), you may use [Promise.fromCallback](.).
Also see [this StackOverflow question](http://stackoverflow.com/questions/23803743/what-is-the-deferred-antipattern-and-how-do-i-avoid-it) for more examples and a debate around it.
##The `.then(success, fail)` anti-pattern
*Almost* a sure sign of using promises as glorified callbacks. Instead of `doThat(function(err, success))` you do `doThat().then(success, err)` and rationalize to yourself that at least the code is "less coupled" or something.
The `.then` signature is mostly about interop, there is *almost* never a reason to use `.then(success, fail)` in application code. It is even awkward to express it in the sync parallel:
```js
var t0;
try {
t0 = doThat();
}
catch(e) {
}
//deal with t0 here and waste the try-catch
var stuff = JSON.parse(t0);
```
It is more likely that you would write this instead in the sync world:
```js
try {
var stuff = JSON.parse(doThat());
}
catch(e) {
}
```
So please write the same when using promises too:
```js
doThat()
.then(function(v) {
return JSON.parse(v);
})
.catch(function(e) {
});
```
`.catch` is specified for built-in Javascript promises and is "sugar" for `.then(null, function(){})`. Since the way errors work in promises is almost the entire point (and the only thing jQuery never got right, even if it used `.pipe` as a `.then`), I really hope the implementation you are using provides this method for readability.

View File

@ -0,0 +1,95 @@
---
id: api-reference
title: API Reference
redirect_from: "/docs/api/index.html"
---
<div class="api-reference-menu">
<markdown>
- [Core](api/core.html)
- [new Promise](api/new-promise.html)
- [.then](api/then.html)
- [.spread](api/spread.html)
- [.catch](api/catch.html)
- [.error](api/error.html)
- [.finally](api/finally.html)
- [.bind](api/bind.html)
- [Promise.join](api/promise.join.html)
- [Promise.try](api/promise.try.html)
- [Promise.method](api/promise.method.html)
- [Promise.resolve](api/promise.resolve.html)
- [Promise.reject](api/promise.reject.html)
- [Synchronous inspection](api/synchronous-inspection.html)
- [PromiseInspection](api/promiseinspection.html)
- [.isFulfilled](api/isfulfilled.html)
- [.isRejected](api/isrejected.html)
- [.isPending](api/ispending.html)
- [.isCancelled](api/iscancelled.html)
- [.value](api/value.html)
- [.reason](api/reason.html)
- [Collections](api/collections.html)
- [Promise.all](api/promise.all.html)
- [Promise.props](api/promise.props.html)
- [Promise.any](api/promise.any.html)
- [Promise.some](api/promise.some.html)
- [Promise.map](api/promise.map.html)
- [Promise.reduce](api/promise.reduce.html)
- [Promise.filter](api/promise.filter.html)
- [Promise.each](api/promise.each.html)
- [Promise.mapSeries](api/promise.mapseries.html)
- [Promise.race](api/promise.race.html)
- [.all](api/all.html)
- [.props](api/props.html)
- [.any](api/any.html)
- [.some](api/some.html)
- [.map](api/map.html)
- [.reduce](api/reduce.html)
- [.filter](api/filter.html)
- [.each](api/each.html)
- [.mapSeries](api/mapseries.html)
- [Resource management](api/resource-management.html)
- [Promise.using](api/promise.using.html)
- [.disposer](api/disposer.html)
- [Promisification](api/promisification.html)
- [Promise.promisify](api/promise.promisify.html)
- [Promise.promisifyAll](api/promise.promisifyall.html)
- [Promise.fromCallback](api/promise.fromcallback.html)
- [.asCallback](api/ascallback.html)
- [Timers](api/timers.html)
- [.delay](api/delay.html)
- [.timeout](api/timeout.html)
- [Cancellation](api/cancellation.html)
- [.cancel](api/cancel.html)
- [Generators](api/generators.html)
- [Promise.coroutine](api/promise.coroutine.html)
- [Promise.coroutine.addYieldHandler](api/promise.coroutine.addyieldhandler.html)
- [Utility](api/utility.html)
- [.tap](api/tap.html)
- [.call](api/call.html)
- [.get](api/get.html)
- [.return](api/return.html)
- [.throw](api/throw.html)
- [.catchReturn](api/catchreturn.html)
- [.catchThrow](api/catchthrow.html)
- [.reflect](api/reflect.html)
- [Promise.noConflict](api/promise.noconflict.html)
- [Promise.setScheduler](api/promise.setscheduler.html)
- [Built-in error types](api/built-in-error-types.html)
- [OperationalError](api/operationalerror.html)
- [TimeoutError](api/timeouterror.html)
- [CancellationError](api/cancellationerror.html)
- [AggregateError](api/aggregateerror.html)
- [Configuration](api/error-management-configuration.html)
- [Global rejection events](api/error-management-configuration.html#global-rejection-events)
- [Local rejection events](api/promise.onpossiblyunhandledrejection.html)
- [Promise.config](api/promise.config.html)
- [.suppressUnhandledRejections](api/suppressunhandledrejections.html)
- [.done](api/done.html)
- [Progression migration](api/progression-migration.html)
- [Deferred migration](api/deferred-migration.html)
- [Environment variables](api/environment-variables.html)
</markdown>
</div>

View File

@ -0,0 +1,49 @@
---
layout: api
id: aggregateerror
title: AggregateError
---
[← Back To API Reference](/docs/api-reference.html)
<div class="api-code-section"><markdown>
##AggregateError
```js
new AggregateError() extends Array -> AggregateError
```
A collection of errors. `AggregateError` is an array-like object, with numeric indices and a `.length` property. It supports all generic array methods such as `.forEach` directly.
`AggregateError`s are caught in [`.error`](.) handlers, even if the contained errors are not operational.
[Promise.some](.) and [Promise.any](.) use `AggregateError` as rejection reason when they fail.
Example:
```js
//Assumes AggregateError has been made global
var err = new AggregateError();
err.push(new Error("first error"));
err.push(new Error("second error"));
throw err;
```
<hr>
</markdown></div>
<div id="disqus_thread"></div>
<script type="text/javascript">
var disqus_title = "AggregateError";
var disqus_shortname = "bluebirdjs";
var disqus_identifier = "disqus-id-aggregateerror";
(function() {
var dsq = document.createElement("script"); dsq.type = "text/javascript"; dsq.async = true;
dsq.src = "//" + disqus_shortname + ".disqus.com/embed.js";
(document.getElementsByTagName("head")[0] || document.getElementsByTagName("body")[0]).appendChild(dsq);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript" rel="nofollow">comments powered by Disqus.</a></noscript>

View File

@ -0,0 +1,31 @@
---
layout: api
id: all
title: .all
---
[← Back To API Reference](/docs/api-reference.html)
<div class="api-code-section"><markdown>
##.all
```js
.all() -> Promise
```
Same as [Promise.all(this)](.).
</markdown></div>
<div id="disqus_thread"></div>
<script type="text/javascript">
var disqus_title = ".all";
var disqus_shortname = "bluebirdjs";
var disqus_identifier = "disqus-id-all";
(function() {
var dsq = document.createElement("script"); dsq.type = "text/javascript"; dsq.async = true;
dsq.src = "//" + disqus_shortname + ".disqus.com/embed.js";
(document.getElementsByTagName("head")[0] || document.getElementsByTagName("body")[0]).appendChild(dsq);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript" rel="nofollow">comments powered by Disqus.</a></noscript>

View File

@ -0,0 +1,31 @@
---
layout: api
id: any
title: .any
---
[← Back To API Reference](/docs/api-reference.html)
<div class="api-code-section"><markdown>
##.any
```js
.any() -> Promise
```
Same as [Promise.any(this)](.).
</markdown></div>
<div id="disqus_thread"></div>
<script type="text/javascript">
var disqus_title = ".any";
var disqus_shortname = "bluebirdjs";
var disqus_identifier = "disqus-id-any";
(function() {
var dsq = document.createElement("script"); dsq.type = "text/javascript"; dsq.async = true;
dsq.src = "//" + disqus_shortname + ".disqus.com/embed.js";
(document.getElementsByTagName("head")[0] || document.getElementsByTagName("body")[0]).appendChild(dsq);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript" rel="nofollow">comments powered by Disqus.</a></noscript>

View File

@ -0,0 +1,114 @@
---
layout: api
id: ascallback
title: .asCallback
---
[← Back To API Reference](/docs/api-reference.html)
<div class="api-code-section"><markdown>
##.asCallback
```js
.asCallback(
[function(any error, any value) callback],
[Object {spread: boolean=false} options]
) -> this
```
```js
.nodeify(
[function(any error, any value) callback],
[Object {spread: boolean=false} options]
) -> this
```
Register a node-style callback on this promise. When this promise is either fulfilled or rejected, the node callback will be called back with the node.js convention where error reason is the first argument and success value is the second argument. The error argument will be `null` in case of success.
Returns back this promise instead of creating a new one. If the `callback` argument is not a function, this method does not do anything.
This can be used to create APIs that both accept node-style callbacks and return promises:
```js
function getDataFor(input, callback) {
return dataFromDataBase(input).asCallback(callback);
}
```
The above function can then make everyone happy.
Promises:
```js
getDataFor("me").then(function(dataForMe) {
console.log(dataForMe);
});
```
Normal callbacks:
```js
getDataFor("me", function(err, dataForMe) {
if( err ) {
console.error( err );
}
console.log(dataForMe);
});
```
Promises can be rejected with falsy values (or no value at all, equal to rejecting with `undefined`), however `.asCallback` will call the callback with an `Error` object if the promise's rejection reason is a falsy value. You can retrieve the original falsy value from the error's `.cause` property.
Example:
```js
Promise.reject(null).asCallback(function(err, result) {
// If is executed
if (err) {
// Logs 'null'
console.log(err.cause);
}
});
```
There is no effect on performance if the user doesn't actually pass a node-style callback function.
####Option: spread
Some nodebacks expect more than 1 success value but there is no mapping for this in the promise world. You may specify the option `spread` to call the nodeback with multiple values when the fulfillment value is an array:
```js
Promise.resolve([1,2,3]).asCallback(function(err, result) {
// err == null
// result is the array [1,2,3]
});
Promise.resolve([1,2,3]).asCallback(function(err, a, b, c) {
// err == null
// a == 1
// b == 2
// c == 3
}, {spread: true});
Promise.resolve(123).asCallback(function(err, a, b, c) {
// err == null
// a == 123
// b == undefined
// c == undefined
}, {spread: true});
```
<hr>
</markdown></div>
<div id="disqus_thread"></div>
<script type="text/javascript">
var disqus_title = ".asCallback";
var disqus_shortname = "bluebirdjs";
var disqus_identifier = "disqus-id-ascallback";
(function() {
var dsq = document.createElement("script"); dsq.type = "text/javascript"; dsq.async = true;
dsq.src = "//" + disqus_shortname + ".disqus.com/embed.js";
(document.getElementsByTagName("head")[0] || document.getElementsByTagName("body")[0]).appendChild(dsq);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript" rel="nofollow">comments powered by Disqus.</a></noscript>

View File

@ -0,0 +1,31 @@
---
layout: api
id: bind
title: .bind
---
[← Back To API Reference](/docs/api-reference.html)
<div class="api-code-section"><markdown>
##.bind
```js
.bind(any|Promise<any> thisArg) -> BoundPromise
```
Same as calling [Promise.bind(thisArg, thisPromise)](.).
</markdown></div>
<div id="disqus_thread"></div>
<script type="text/javascript">
var disqus_title = ".bind";
var disqus_shortname = "bluebirdjs";
var disqus_identifier = "disqus-id-bind";
(function() {
var dsq = document.createElement("script"); dsq.type = "text/javascript"; dsq.async = true;
dsq.src = "//" + disqus_shortname + ".disqus.com/embed.js";
(document.getElementsByTagName("head")[0] || document.getElementsByTagName("body")[0]).appendChild(dsq);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript" rel="nofollow">comments powered by Disqus.</a></noscript>

View File

@ -0,0 +1,31 @@
---
layout: api
id: built-in-error-types
title: Built-in error types
---
[← Back To API Reference](/docs/api-reference.html)
<div class="api-code-section"><markdown>
##Built-in error types
Bluebird includes a few built-in error types for common usage. All error types have the same identity across different copies of bluebird
module so that pattern matching works in [`.catch`](.). All error types have a constructor taking a message string as their first argument, with that message
becoming the `.message` property of the error object.
By default the error types need to be referenced from the Promise constructor, e.g. to get a reference to [TimeoutError](.), do `var TimeoutError = Promise.TimeoutError`. However, for convenience you will probably want to just make the references global.
</markdown></div>
<div id="disqus_thread"></div>
<script type="text/javascript">
var disqus_title = "Built-in error types";
var disqus_shortname = "bluebirdjs";
var disqus_identifier = "disqus-id-built-in-error-types";
(function() {
var dsq = document.createElement("script"); dsq.type = "text/javascript"; dsq.async = true;
dsq.src = "//" + disqus_shortname + ".disqus.com/embed.js";
(document.getElementsByTagName("head")[0] || document.getElementsByTagName("body")[0]).appendChild(dsq);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript" rel="nofollow">comments powered by Disqus.</a></noscript>

View File

@ -0,0 +1,101 @@
---
layout: api
id: call
title: .call
---
[← Back To API Reference](/docs/api-reference.html)
<div class="api-code-section"><markdown>
##.call
```js
.call(
String methodName,
[any args...]
)
```
This is a convenience method for doing:
```js
promise.then(function(obj) {
return obj[methodName].call(obj, arg...);
});
```
For example ([`some` is a built-in array method](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/some)):
```js
var Promise = require("bluebird");
var fs = Promise.promisifyAll(require("fs"));
var path = require("path");
var thisPath = process.argv[2] || ".";
var now = Date.now();
fs.readdirAsync(thisPath)
.map(function(fileName) {
return fs.statAsync(path.join(thisPath, fileName));
})
.call("some", function(stat) {
return (now - new Date(stat.mtime)) < 10000;
})
.then(function(someFilesHaveBeenModifiedLessThanTenSecondsAgo) {
console.log(someFilesHaveBeenModifiedLessThanTenSecondsAgo) ;
});
```
Chaining lo-dash or underscore methods (Copy-pasteable example):
```js
var Promise = require("bluebird");
var pmap = Promise.map;
var props = Promise.props;
var _ = require("lodash");
var fs = Promise.promisifyAll(require("fs"));
function getTotalSize(paths) {
return pmap(paths, function(path) {
return fs.statAsync(path).get("size");
}).reduce(function(a, b) {
return a + b;
}, 0);
}
fs.readdirAsync(".").then(_)
.call("groupBy", function(fileName) {
return fileName.charAt(0);
})
.call("map", function(fileNames, firstCh) {
return props({
firstCh: firstCh,
count: fileNames.length,
totalSize: getTotalSize(fileNames)
});
})
// Since the currently wrapped array contains promises we need to unwrap it and call .all() before continuing the chain
// If the currently wrapped thing was an object with properties that might be promises, we would call .props() instead
.call("value").all().then(_)
.call("sortBy", "count")
.call("reverse")
.call("map", function(data) {
return data.count + " total files beginning with " + data.firstCh + " with total size of " + data.totalSize + " bytes";
})
.call("join", "\n")
.then(console.log)
```
</markdown></div>
<div id="disqus_thread"></div>
<script type="text/javascript">
var disqus_title = ".call";
var disqus_shortname = "bluebirdjs";
var disqus_identifier = "disqus-id-call";
(function() {
var dsq = document.createElement("script"); dsq.type = "text/javascript"; dsq.async = true;
dsq.src = "//" + disqus_shortname + ".disqus.com/embed.js";
(document.getElementsByTagName("head")[0] || document.getElementsByTagName("body")[0]).appendChild(dsq);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript" rel="nofollow">comments powered by Disqus.</a></noscript>

View File

@ -0,0 +1,33 @@
---
layout: api
id: cancel
title: .cancel
---
[← Back To API Reference](/docs/api-reference.html)
<div class="api-code-section"><markdown>
##.cancel
```js
.cancel() -> undefined
```
Cancel this promise. Will not do anything if this promise is already settled or if the [Cancellation](.) feature has not been enabled. See [Cancellation](.) for how to use cancellation.
<hr>
</markdown></div>
<div id="disqus_thread"></div>
<script type="text/javascript">
var disqus_title = ".cancel";
var disqus_shortname = "bluebirdjs";
var disqus_identifier = "disqus-id-cancel";
(function() {
var dsq = document.createElement("script"); dsq.type = "text/javascript"; dsq.async = true;
dsq.src = "//" + disqus_shortname + ".disqus.com/embed.js";
(document.getElementsByTagName("head")[0] || document.getElementsByTagName("body")[0]).appendChild(dsq);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript" rel="nofollow">comments powered by Disqus.</a></noscript>

View File

@ -0,0 +1,112 @@
---
layout: api
id: cancellation
title: Cancellation
---
[← Back To API Reference](/docs/api-reference.html)
<div class="api-code-section"><markdown>
##Cancellation
Cancellation has been redesigned for bluebird 3.x, any code that relies on 2.x cancellation semantics won't work in 3.x.
The cancellation feature is **by default turned off**, you can enable it using [Promise.config](.).
The new cancellation has "don't care" semantics while the old cancellation had abort semantics. Cancelling a promise simply means that its handler callbacks will not be called.
The advantages of the new cancellation compared to the old cancellation are:
- [.cancel()](.) is synchronous.
- no setup code required to make cancellation work
- composes with other bluebird features, like [Promise.all](.).
- [reasonable semantics for multiple consumer cancellation](#what-about-promises-that-have-multiple-consumers)
As an optimization, the cancellation signal propagates upwards the promise chain so that an ongoing operation e.g. network request can be aborted. However, *not* aborting the network request still doesn't make any operational difference as the callbacks are still not called either way.
You may register an optional cancellation hook at a root promise by using the `onCancel` argument that is passed to the executor function when cancellation is enabled:
```js
function makeCancellableRequest(url) {
return new Promise(function(resolve, reject, onCancel) {
var xhr = new XMLHttpRequest();
xhr.on("load", resolve);
xhr.on("error", reject);
xhr.open("GET", url, true);
xhr.send(null);
// Note the onCancel argument only exists if cancellation has been enabled!
onCancel(function() {
xhr.abort();
});
});
}
```
Note that the `onCancel` hook is really an optional disconnected optimization, there is no real requirement to register any cancellation hooks for cancellation to work. As such, any errors that may occur while inside the `onCancel` callback are not caught and turned into rejections.
Example:
```js
var searchPromise = Promise.resolve(); // Dummy promise to avoid null check.
document.querySelector("#search-input").addEventListener("input", function() {
// The handlers of the previous request must not be called
searchPromise.cancel();
var url = "/search?term=" + encodeURIComponent(this.value.trim());
showSpinner();
searchPromise = makeCancellableRequest(url)
.then(function(results) {
return transformData(results);
})
.then(function(transformedData) {
document.querySelector("#search-results").innerHTML = transformedData;
})
.catch(function(e) {
document.querySelector("#search-results").innerHTML = renderErrorBox(e);
})
.finally(function() {
// This check is necessary because `.finally` handlers are always called.
if (!searchPromise.isCancelled()) {
hideSpinner();
}
});
});
```
As shown in the example the handlers registered with `.finally` are called even if the promise is cancelled. Another such exception is [.reflect()](.). No other types of handlers will be called in case of cancellation. This means that in `.then(onSuccess, onFailure)` neither `onSuccess` or `onFailure` handler is called. This is similar to how [`Generator#return`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator/return) works - only active `finally` blocks are executed and then the generator exits.
###What about promises that have multiple consumers?
It is often said that promises cannot be cancellable because they can have multiple consumers.
For instance:
```js
var result = makeCancellableRequest(...);
var firstConsumer = result.then(...);
var secondConsumer = result.then(...);
```
Even though in practice most users of promises will never have any need to take advantage of the fact that you can attach multiple consumers to a promise, it is nevertheless possible. The problem: "what should happen if [.cancel()](.) is called on `firstConsumer`?" Propagating the cancellation signal (and therefore making it abort the request) would be very bad as the second consumer might still be interested in the result despite the first consumer's disinterest.
What actually happens is that `result` keeps track of how many consumers it has, in this case 2, and only if all the consumers signal cancel will the request be aborted. However, as far as `firstConsumer` can tell, the promise was successfully cancelled and its handlers will not be called.
Note that it is an error to consume an already cancelled promise, doing such a thing will give you a promise that is rejected with `new CancellationError("late cancellation observer")` as the rejection reason.
<hr>
</markdown></div>
<div id="disqus_thread"></div>
<script type="text/javascript">
var disqus_title = "Cancellation";
var disqus_shortname = "bluebirdjs";
var disqus_identifier = "disqus-id-cancellation";
(function() {
var dsq = document.createElement("script"); dsq.type = "text/javascript"; dsq.async = true;
dsq.src = "//" + disqus_shortname + ".disqus.com/embed.js";
(document.getElementsByTagName("head")[0] || document.getElementsByTagName("body")[0]).appendChild(dsq);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript" rel="nofollow">comments powered by Disqus.</a></noscript>

View File

@ -0,0 +1,32 @@
---
layout: api
id: cancellationerror
title: CancellationError
---
[← Back To API Reference](/docs/api-reference.html)
<div class="api-code-section"><markdown>
##CancellationError
```js
new CancellationError(String message) -> CancellationError
```
Signals that an operation has been aborted or cancelled. The default reason used by [`.cancel`](.).
</markdown></div>
<div id="disqus_thread"></div>
<script type="text/javascript">
var disqus_title = "CancellationError";
var disqus_shortname = "bluebirdjs";
var disqus_identifier = "disqus-id-cancellationerror";
(function() {
var dsq = document.createElement("script"); dsq.type = "text/javascript"; dsq.async = true;
dsq.src = "//" + disqus_shortname + ".disqus.com/embed.js";
(document.getElementsByTagName("head")[0] || document.getElementsByTagName("body")[0]).appendChild(dsq);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript" rel="nofollow">comments powered by Disqus.</a></noscript>

View File

@ -0,0 +1,227 @@
---
layout: api
id: catch
title: .catch
---
[← Back To API Reference](/docs/api-reference.html)
<div class="api-code-section"><markdown>
##.catch
`.catch` is a convenience method for handling errors in promise chains.
It comes in two variants
- A catch-all variant similar to the synchronous `catch(e) {` block. This variant is compatible with native promises.
- A filtered variant (like other non-JS languages typically have) that lets you only handle specific errors. **This variant is usually preferable and is significantly safer**.
### A note on promise exception handling.
Promise exception handling mirrors native exception handling in JavaScript. A synchronous function `throw`ing is similar to a promise rejecting. Here is an example to illustrate it:
```js
function getItems(parma) {
try {
var items = getItemsSync();
if(!items) throw new InvalidItemsError();
} catch(e) {
// can address the error here, either from getItemsSync returning a falsey value or throwing itself
throw e; // need to re-throw the error unless I want it to be considered handled.
}
return process(items);
}
```
Similarly, with promises:
```js
function getItems(param) {
return getItemsAsync().then(items => {
if(!items) throw new InvalidItemsError();
return items;
}).catch(e => {
// can address the error here and recover from it, from getItemsAsync rejects or returns a falsey value
throw e; // Need to rethrow unless we actually recovered, just like in the synchronous version
}).then(process);
}
```
### Catch-all
```js
.catch(function(any error) handler) -> Promise
```
```js
.caught(function(any error) handler) -> Promise
```
This is a catch-all exception handler, shortcut for calling [`.then(null, handler)`](.) on this promise. Any exception happening in a `.then`-chain will propagate to nearest `.catch` handler.
*For compatibility with earlier ECMAScript versions, an alias `.caught` is provided for [`.catch`](.).*
### Filtered Catch
```js
.catch(
class ErrorClass|function(any error)|Object predicate...,
function(any error) handler
) -> Promise
```
```js
.caught(
class ErrorClass|function(any error)|Object predicate...,
function(any error) handler
) -> Promise
```
This is an extension to [`.catch`](.) to work more like catch-clauses in languages like Java or C#. Instead of manually checking `instanceof` or `.name === "SomeError"`, you may specify a number of error constructors which are eligible for this catch handler. The catch handler that is first met that has eligible constructors specified, is the one that will be called.
Example:
```js
somePromise.then(function() {
return a.b.c.d();
}).catch(TypeError, function(e) {
//If it is a TypeError, will end up here because
//it is a type error to reference property of undefined
}).catch(ReferenceError, function(e) {
//Will end up here if a was never declared at all
}).catch(function(e) {
//Generic catch-the rest, error wasn't TypeError nor
//ReferenceError
});
```
You may also add multiple filters for a catch handler:
```js
somePromise.then(function() {
return a.b.c.d();
}).catch(TypeError, ReferenceError, function(e) {
//Will end up here on programmer error
}).catch(NetworkError, TimeoutError, function(e) {
//Will end up here on expected everyday network errors
}).catch(function(e) {
//Catch any unexpected errors
});
```
For a parameter to be considered a type of error that you want to filter, you need the constructor to have its `.prototype` property be `instanceof Error`.
Such a constructor can be minimally created like so:
```js
function MyCustomError() {}
MyCustomError.prototype = Object.create(Error.prototype);
```
Using it:
```js
Promise.resolve().then(function() {
throw new MyCustomError();
}).catch(MyCustomError, function(e) {
//will end up here now
});
```
However if you want stack traces and cleaner string output, then you should do:
*in Node.js and other V8 environments, with support for `Error.captureStackTrace`*
```js
function MyCustomError(message) {
this.message = message;
this.name = "MyCustomError";
Error.captureStackTrace(this, MyCustomError);
}
MyCustomError.prototype = Object.create(Error.prototype);
MyCustomError.prototype.constructor = MyCustomError;
```
Using CoffeeScript's `class` for the same:
```coffee
class MyCustomError extends Error
constructor: (@message) ->
@name = "MyCustomError"
Error.captureStackTrace(this, MyCustomError)
```
This method also supports predicate-based filters. If you pass a
predicate function instead of an error constructor, the predicate will receive
the error as an argument. The return result of the predicate will be used
determine whether the error handler should be called.
Predicates should allow for very fine grained control over caught errors:
pattern matching, error-type sets with set operations and many other techniques
can be implemented on top of them.
Example of using a predicate-based filter:
```js
var Promise = require("bluebird");
var request = Promise.promisify(require("request"));
function ClientError(e) {
return e.code >= 400 && e.code < 500;
}
request("http://www.google.com").then(function(contents) {
console.log(contents);
}).catch(ClientError, function(e) {
//A client error like 400 Bad Request happened
});
```
Predicate functions that only check properties have a handy shorthand. In place of a predicate function, you can pass an object, and its properties will be checked against the error object for a match:
```js
fs.readFileAsync(...)
.then(...)
.catch({code: 'ENOENT'}, function(e) {
console.log("file not found: " + e.path);
});
```
The object predicate passed to `.catch` in the above code (`{code: 'ENOENT'}`) is shorthand for a predicate function `function predicate(e) { return isObject(e) && e.code == 'ENOENT' }`, I.E. loose equality is used.
*For compatibility with earlier ECMAScript version, an alias `.caught` is provided for [`.catch`](.).*
</markdown></div>
By not returning a rejected value or `throw`ing from a catch, you "recover from failure" and continue the chain:
```js
Promise.reject(Error('fail!'))
.catch(function(e) {
// fallback with "recover from failure"
return Promise.resolve('success!'); // promise or value
})
.then(function(result) {
console.log(result); // will print "success!"
});
```
This is exactly like the synchronous code:
```js
var result;
try {
throw Error('fail');
} catch(e) {
result = 'success!';
}
console.log(result);
```
<div id="disqus_thread"></div>
<script type="text/javascript">
var disqus_title = ".catch";
var disqus_shortname = "bluebirdjs";
var disqus_identifier = "disqus-id-catch";
(function() {
var dsq = document.createElement("script"); dsq.type = "text/javascript"; dsq.async = true;
dsq.src = "//" + disqus_shortname + ".disqus.com/embed.js";
(document.getElementsByTagName("head")[0] || document.getElementsByTagName("body")[0]).appendChild(dsq);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript" rel="nofollow">comments powered by Disqus.</a></noscript>

View File

@ -0,0 +1,43 @@
---
layout: api
id: catchreturn
title: .catchReturn
---
[← Back To API Reference](/docs/api-reference.html)
<div class="api-code-section"><markdown>
##.catchReturn
```js
.catchReturn(
[class ErrorClass|function(any error) predicate],
any value
) -> Promise
```
Convenience method for:
```js
.catch(function() {
return value;
});
```
You may optionally prepend one predicate function or ErrorClass to pattern match the error (the generic [.catch](.) methods accepts multiple)
Same limitations regarding to the binding time of `value` to apply as with [`.return`](.).
</markdown></div>
<div id="disqus_thread"></div>
<script type="text/javascript">
var disqus_title = ".catchReturn";
var disqus_shortname = "bluebirdjs";
var disqus_identifier = "disqus-id-catchreturn";
(function() {
var dsq = document.createElement("script"); dsq.type = "text/javascript"; dsq.async = true;
dsq.src = "//" + disqus_shortname + ".disqus.com/embed.js";
(document.getElementsByTagName("head")[0] || document.getElementsByTagName("body")[0]).appendChild(dsq);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript" rel="nofollow">comments powered by Disqus.</a></noscript>

View File

@ -0,0 +1,43 @@
---
layout: api
id: catchthrow
title: .catchThrow
---
[← Back To API Reference](/docs/api-reference.html)
<div class="api-code-section"><markdown>
##.catchThrow
```js
.catchThrow(
[class ErrorClass|function(any error) predicate],
any reason
) -> Promise
```
Convenience method for:
```js
.catch(function() {
throw reason;
});
```
You may optionally prepend one predicate function or ErrorClass to pattern match the error (the generic [.catch](.) methods accepts multiple)
Same limitations regarding to the binding time of `reason` to apply as with [`.return`](.).
</markdown></div>
<div id="disqus_thread"></div>
<script type="text/javascript">
var disqus_title = ".catchThrow";
var disqus_shortname = "bluebirdjs";
var disqus_identifier = "disqus-id-catchthrow";
(function() {
var dsq = document.createElement("script"); dsq.type = "text/javascript"; dsq.async = true;
dsq.src = "//" + disqus_shortname + ".disqus.com/embed.js";
(document.getElementsByTagName("head")[0] || document.getElementsByTagName("body")[0]).appendChild(dsq);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript" rel="nofollow">comments powered by Disqus.</a></noscript>

View File

@ -0,0 +1,32 @@
---
layout: api
id: collections
title: Collections
---
[← Back To API Reference](/docs/api-reference.html)
<div class="api-code-section"><markdown>
##Collections
Methods of `Promise` instances and core static methods of the Promise class to deal with collections of promises or mixed promises and values.
All collection methods have a static equivalent on the Promise object, e.g. `somePromise.map(...)...` is same as `Promise.map(somePromise, ...)...`,
`somePromise.all` is same as [`Promise.all`](.) and so on.
None of the collection methods modify the original input. Holes in arrays are treated as if they were defined with the value `undefined`.
</markdown></div>
<div id="disqus_thread"></div>
<script type="text/javascript">
var disqus_title = "Collections";
var disqus_shortname = "bluebirdjs";
var disqus_identifier = "disqus-id-collections";
(function() {
var dsq = document.createElement("script"); dsq.type = "text/javascript"; dsq.async = true;
dsq.src = "//" + disqus_shortname + ".disqus.com/embed.js";
(document.getElementsByTagName("head")[0] || document.getElementsByTagName("body")[0]).appendChild(dsq);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript" rel="nofollow">comments powered by Disqus.</a></noscript>

View File

@ -0,0 +1,27 @@
---
layout: api
id: core
title: Core
---
[← Back To API Reference](/docs/api-reference.html)
<div class="api-code-section"><markdown>
##Core
Core methods of `Promise` instances and core static methods of the Promise class.
</markdown></div>
<div id="disqus_thread"></div>
<script type="text/javascript">
var disqus_title = "Core";
var disqus_shortname = "bluebirdjs";
var disqus_identifier = "disqus-id-core";
(function() {
var dsq = document.createElement("script"); dsq.type = "text/javascript"; dsq.async = true;
dsq.src = "//" + disqus_shortname + ".disqus.com/embed.js";
(document.getElementsByTagName("head")[0] || document.getElementsByTagName("body")[0]).appendChild(dsq);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript" rel="nofollow">comments powered by Disqus.</a></noscript>

View File

@ -0,0 +1,44 @@
---
layout: api
id: deferred-migration
title: Deferred migration
---
[← Back To API Reference](/docs/api-reference.html)
<div class="api-code-section"><markdown>
##Deferred migration
Deferreds are deprecated in favor of the promise constructor. If you need deferreds for some reason, you can create them trivially using the constructor:
```js
function defer() {
var resolve, reject;
var promise = new Promise(function() {
resolve = arguments[0];
reject = arguments[1];
});
return {
resolve: resolve,
reject: reject,
promise: promise
};
}
```
For old code that still uses deferred objects, see [the deprecated API docs ](/bluebird/web/docs/deprecated_apis.html#promise-resolution).
</markdown></div>
<div id="disqus_thread"></div>
<script type="text/javascript">
var disqus_title = "Deferred migration";
var disqus_shortname = "bluebirdjs";
var disqus_identifier = "disqus-id-deferred-migration";
(function() {
var dsq = document.createElement("script"); dsq.type = "text/javascript"; dsq.async = true;
dsq.src = "//" + disqus_shortname + ".disqus.com/embed.js";
(document.getElementsByTagName("head")[0] || document.getElementsByTagName("body")[0]).appendChild(dsq);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript" rel="nofollow">comments powered by Disqus.</a></noscript>

View File

@ -0,0 +1,31 @@
---
layout: api
id: delay
title: .delay
---
[← Back To API Reference](/docs/api-reference.html)
<div class="api-code-section"><markdown>
##.delay
```js
.delay(int ms) -> Promise
```
Same as calling [Promise.delay(ms, this)](.).
</markdown></div>
<div id="disqus_thread"></div>
<script type="text/javascript">
var disqus_title = ".delay";
var disqus_shortname = "bluebirdjs";
var disqus_identifier = "disqus-id-delay";
(function() {
var dsq = document.createElement("script"); dsq.type = "text/javascript"; dsq.async = true;
dsq.src = "//" + disqus_shortname + ".disqus.com/embed.js";
(document.getElementsByTagName("head")[0] || document.getElementsByTagName("body")[0]).appendChild(dsq);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript" rel="nofollow">comments powered by Disqus.</a></noscript>

View File

@ -0,0 +1,158 @@
---
layout: api
id: disposer
title: .disposer
---
[← Back To API Reference](/docs/api-reference.html)
<div class="api-code-section"><markdown>
##.disposer
```js
.disposer(function(any resource, Promise usingOutcomePromise) disposer) -> Disposer
```
A meta method used to specify the disposer method that cleans up a resource when using `Promise.using`.
Returns a Disposer object which encapsulates both the resource as well as the method to clean it up. The user can pass this object to `Promise.using` to get access to the resource when it becomes available, as well as to ensure its automatically cleaned up.
The second argument passed to a disposer is the result promise of the using block, which you can inspect synchronously.
Example:
```js
// This function doesn't return a promise but a Disposer
// so it's very hard to use it wrong (not passing it to `using`)
function getConnection() {
return db.connect().disposer(function(connection, promise) {
connection.close();
});
}
```
In the above example, the connection returned by `getConnection` can only be
used via `Promise.using`, like so:
```js
function useConnection(query) {
return Promise.using(getConnection(), function(connection) {
return connection.sendQuery(query).then(function(results) {
return process(results);
})
});
}
```
This will ensure that `connection.close()` will be called once the promise returned
from the `Promise.using` closure is resolved or if an exception was thrown in the closure
body.
Real example:
```js
var pg = require("pg");
// Uncomment if pg has not been properly promisified yet
//var Promise = require("bluebird");
//Promise.promisifyAll(pg, {
// filter: function(methodName) {
// return methodName === "connect"
// },
// multiArgs: true
//});
// Promisify rest of pg normally
//Promise.promisifyAll(pg);
function getSqlConnection(connectionString) {
var close;
return pg.connectAsync(connectionString).spread(function(client, done) {
close = done;
return client;
}).disposer(function() {
if (close) close();
});
}
module.exports = getSqlConnection;
```
Real example 2:
```js
var mysql = require("mysql");
// Uncomment if pg has not been properly promisified yet
// var Promise = require("bluebird");
// Promise.promisifyAll(mysql);
// Promise.promisifyAll(require("mysql/lib/Connection").prototype);
// Promise.promisifyAll(require("mysql/lib/Pool").prototype);
var pool = mysql.createPool({
connectionLimit: 10,
host: 'example.org',
user: 'bob',
password: 'secret'
});
function getSqlConnection() {
return pool.getConnectionAsync().disposer(function(connection) {
connection.release();
});
}
module.exports = getSqlConnection;
```
#### Note about disposers in node
If a disposer method throws or returns a rejected promise, its highly likely that it failed to dispose of the resource. In that case, Bluebird has two options - it can either ignore the error and continue with program execution or throw an exception (crashing the process in node.js).
In bluebird we've chosen to do the latter because resources are typically scarce. For example, if a database connection cannot be disposed of and Bluebird ignores that, the connection pool will be quickly depleted and the process will become unusable (all requests that query the database will wait forever). Since Bluebird doesn't know how to handle that, the only sensible default is to crash the process. That way, rather than getting a useless process that cannot fulfill more requests, we can swap the faulty worker with a new one letting the OS clean up the resources for us.
As a result, if you anticipate thrown errors or promise rejections while disposing of the resource you should use a `try..catch` block (or Promise.try) and write the appropriate catch code to handle the errors. If its not possible to sensibly handle the error, letting the process crash is the next best option.
This also means that disposers should not contain code that does anything other than resource disposal. For example, you cannot write code inside a disposer to commit or rollback a transaction, because there is no mechanism for the disposer to signal a failure of the commit or rollback action without crashing the process.
For transactions, you can use the following similar pattern instead:
```js
function withTransaction(fn) {
return Promise.using(pool.acquireConnection(), function(connection) {
var tx = connection.beginTransaction()
return Promise
.try(fn, tx)
.then(function(res) { return connection.commit().thenReturn(res) },
function(err) {
return connection.rollback()
.catch(function(e) {/* maybe add the rollback error to err */})
.thenThrow(err);
});
});
}
// If the withTransaction block completes successfully, the transaction is automatically committed
// Any error or rejection will automatically roll it back
withTransaction(function(tx) {
return tx.queryAsync(...).then(function() {
return tx.queryAsync(...)
}).then(function() {
return tx.queryAsync(...)
});
});
```
<hr>
</markdown></div>
<div id="disqus_thread"></div>
<script type="text/javascript">
var disqus_title = ".disposer";
var disqus_shortname = "bluebirdjs";
var disqus_identifier = "disqus-id-disposer";
(function() {
var dsq = document.createElement("script"); dsq.type = "text/javascript"; dsq.async = true;
dsq.src = "//" + disqus_shortname + ".disqus.com/embed.js";
(document.getElementsByTagName("head")[0] || document.getElementsByTagName("body")[0]).appendChild(dsq);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript" rel="nofollow">comments powered by Disqus.</a></noscript>

View File

@ -0,0 +1,37 @@
---
layout: api
id: done
title: .done
---
[← Back To API Reference](/docs/api-reference.html)
<div class="api-code-section"><markdown>
##.done
```js
.done(
[function(any value) fulfilledHandler],
[function(any error) rejectedHandler]
) -> undefined
```
Like [`.then`](.), but any unhandled rejection that ends up here will crash the process (in node) or be thrown as an error (in browsers). The use of this method is heavily discouraged and it only exists for historical reasons.
<hr>
</markdown></div>
<div id="disqus_thread"></div>
<script type="text/javascript">
var disqus_title = ".done";
var disqus_shortname = "bluebirdjs";
var disqus_identifier = "disqus-id-done";
(function() {
var dsq = document.createElement("script"); dsq.type = "text/javascript"; dsq.async = true;
dsq.src = "//" + disqus_shortname + ".disqus.com/embed.js";
(document.getElementsByTagName("head")[0] || document.getElementsByTagName("body")[0]).appendChild(dsq);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript" rel="nofollow">comments powered by Disqus.</a></noscript>

View File

@ -0,0 +1,51 @@
---
layout: api
id: each
title: .each
---
[← Back To API Reference](/docs/api-reference.html)
<div class="api-code-section"><markdown>
##.each
```js
.each(function(any item, int index, int length) iterator) -> Promise
```
Iterate over an array, or a promise of an array, which contains promises (or a mix of promises and values) with the given `iterator` function with the signature `(value, index, length)` where `value` is the resolved value of a respective promise in the input array. Iteration happens serially. If any promise in the input array is rejected the returned promise is rejected as well.
Resolves to the original array unmodified, this method is meant to be used for side effects. If the iterator function returns a promise or a thenable, then the result of the promise is awaited, before continuing with next iteration.
Example where you might want to utilize `.each`:
```js
// Source: http://jakearchibald.com/2014/es7-async-functions/
function loadStory() {
return getJSON('story.json')
.then(function(story) {
addHtmlToPage(story.heading);
return story.chapterURLs.map(getJSON);
})
.each(function(chapter) { addHtmlToPage(chapter.html); })
.then(function() { addTextToPage("All done"); })
.catch(function(err) { addTextToPage("Argh, broken: " + err.message); })
.then(function() { document.querySelector('.spinner').style.display = 'none'; });
}
```
</markdown></div>
<div id="disqus_thread"></div>
<script type="text/javascript">
var disqus_title = ".each";
var disqus_shortname = "bluebirdjs";
var disqus_identifier = "disqus-id-each";
(function() {
var dsq = document.createElement("script"); dsq.type = "text/javascript"; dsq.async = true;
dsq.src = "//" + disqus_shortname + ".disqus.com/embed.js";
(document.getElementsByTagName("head")[0] || document.getElementsByTagName("body")[0]).appendChild(dsq);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript" rel="nofollow">comments powered by Disqus.</a></noscript>

View File

@ -0,0 +1,41 @@
---
layout: api
id: environment-variables
title: Environment variables
---
[← Back To API Reference](/docs/api-reference.html)
<div class="api-code-section"><markdown>
##Environment variables
This section only applies to node.js or io.js.
You can change bluebird behavior globally with various environment variables. These global variables affect all instances of bluebird that are running in your environment, rather than just the one you have `require`d in your application. The effect an environment variable has depends on the bluebird version.
Environment variables supported by 2.x:
- `BLUEBIRD_DEBUG` - Set to any truthy value this will enable long stack traces and warnings
- `NODE_ENV` - If set exactly to `development` it will have the same effect as if the `BLUEBIRD_DEBUG` variable was set.
Environment variables supported by 3.x:
- `BLUEBIRD_DEBUG` - If set this will enable long stack traces and warnings, unless those are explicitly disabled. Setting this to exactly `0` can be used to override `NODE_ENV=development` enabling long stack traces and warnings.
- `NODE_ENV` - If set exactly to `development` it will have the same effect as if the `BLUEBIRD_DEBUG` variable was set.
- `BLUEBIRD_WARNINGS` - if set exactly to `0` it will explicitly disable warnings and this overrides any other setting that might enable warnings. If set to any truthy value, it will explicitly enable warnings.
- `BLUEBIRD_LONG_STACK_TRACES` - if set exactly to `0` it will explicitly disable long stack traces and this overrides any other setting that might enable long stack traces. If set to any truthy value, it will explicitly enable long stack traces.
</markdown></div>
<div id="disqus_thread"></div>
<script type="text/javascript">
var disqus_title = "Environment variables";
var disqus_shortname = "bluebirdjs";
var disqus_identifier = "disqus-id-environment-variables";
(function() {
var dsq = document.createElement("script"); dsq.type = "text/javascript"; dsq.async = true;
dsq.src = "//" + disqus_shortname + ".disqus.com/embed.js";
(document.getElementsByTagName("head")[0] || document.getElementsByTagName("body")[0]).appendChild(dsq);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript" rel="nofollow">comments powered by Disqus.</a></noscript>

View File

@ -0,0 +1,107 @@
---
layout: api
id: error-management-configuration
title: Error management configuration
---
[← Back To API Reference](/docs/api-reference.html)
<div class="api-code-section"><markdown>
##Error management configuration
The default approach of bluebird is to immediately log the stack trace when there is an unhandled rejection. This is similar to how uncaught exceptions cause the stack trace to be logged so that you have something to work with when something is not working as expected.
However because it is possible to handle a rejected promise at any time in the indeterminate future, some programming patterns will result in false positives. Because such programming patterns are not necessary and can always be refactored to never cause false positives, we recommend doing that to keep debugging as easy as possible . You may however feel differently so bluebird provides hooks to implement more complex failure policies.
Such policies could include:
- Logging after the promise became GCd (requires a native node.js module)
- Showing a live list of rejected promises
- Using no hooks and using [`.done`](.) to manually to mark end points where rejections will not be handled
- Swallowing all errors (challenge your debugging skills)
- ...
<hr>
###Global rejection events
Starting from 2.7.0 all bluebird instances also fire rejection events globally so that applications can register one universal hook for them.
The global events are:
- `"unhandledRejection"` (corresponds to the local [`Promise.onPossiblyUnhandledRejection`](.))
- `"rejectionHandled"` (corresponds to the local [`Promise.onUnhandledRejectionHandled`](.))
Attaching global rejection event handlers in **node.js**:
```js
// NOTE: event name is camelCase as per node convention
process.on("unhandledRejection", function(reason, promise) {
// See Promise.onPossiblyUnhandledRejection for parameter documentation
});
// NOTE: event name is camelCase as per node convention
process.on("rejectionHandled", function(promise) {
// See Promise.onUnhandledRejectionHandled for parameter documentation
});
```
Attaching global rejection event handlers in **browsers**:
Using DOM3 `addEventListener` APIs (support starting from IE9+):
```js
// NOTE: event name is all lower case as per DOM convention
window.addEventListener("unhandledrejection", function(e) {
// NOTE: e.preventDefault() must be manually called to prevent the default
// action which is currently to log the stack trace to console.warn
e.preventDefault();
// NOTE: parameters are properties of the event detail property
var reason = e.detail.reason;
var promise = e.detail.promise;
// See Promise.onPossiblyUnhandledRejection for parameter documentation
});
// NOTE: event name is all lower case as per DOM convention
window.addEventListener("rejectionhandled", function(e) {
// NOTE: e.preventDefault() must be manually called prevent the default
// action which is currently unset (but might be set to something in the future)
e.preventDefault();
// NOTE: parameters are properties of the event detail property
var promise = e.detail.promise;
// See Promise.onUnhandledRejectionHandled for parameter documentation
});
```
In Web Workers you may use `self.addEventListener`.
Using legacy APIs (support starting from IE6+):
```js
// NOTE: event name is all lower case as per legacy convention
window.onunhandledrejection = function(reason, promise) {
// See Promise.onPossiblyUnhandledRejection for parameter documentation
};
// NOTE: event name is all lower case as per legacy convention
window.onrejectionhandled = function(promise) {
// See Promise.onUnhandledRejectionHandled for parameter documentation
};
```
<hr>
</markdown></div>
<div id="disqus_thread"></div>
<script type="text/javascript">
var disqus_title = "Error management configuration";
var disqus_shortname = "bluebirdjs";
var disqus_identifier = "disqus-id-error-management-configuration";
(function() {
var dsq = document.createElement("script"); dsq.type = "text/javascript"; dsq.async = true;
dsq.src = "//" + disqus_shortname + ".disqus.com/embed.js";
(document.getElementsByTagName("head")[0] || document.getElementsByTagName("body")[0]).appendChild(dsq);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript" rel="nofollow">comments powered by Disqus.</a></noscript>

View File

@ -0,0 +1,101 @@
---
layout: api
id: error
title: .error
---
[← Back To API Reference](/docs/api-reference.html)
<div class="api-code-section"><markdown>
##.error
```js
.error([function(any error) rejectedHandler]) -> Promise
```
Like [`.catch`](.) but instead of catching all types of exceptions, it only catches operational errors.
*Note, "errors" mean errors, as in objects that are `instanceof Error` - not strings, numbers and so on. See [a string is not an error](http://www.devthought.com/2011/12/22/a-string-is-not-an-error/).*
It is equivalent to the following [`.catch`](.) pattern:
```js
// Assumes OperationalError has been made global
function isOperationalError(e) {
if (e == null) return false;
return (e instanceof OperationalError) || (e.isOperational === true);
}
// Now this bit:
.catch(isOperationalError, function(e) {
// ...
})
// Is equivalent to:
.error(function(e) {
// ...
});
```
For example, if a promisified function errbacks the node-style callback with an error, that could be caught with [`.error`](.). However if the node-style callback **throws** an error, only `.catch` would catch that.
In the following example you might want to handle just the `SyntaxError` from JSON.parse and Filesystem errors from `fs` but let programmer errors bubble as unhandled rejections:
```js
var fs = Promise.promisifyAll(require("fs"));
fs.readFileAsync("myfile.json").then(JSON.parse).then(function (json) {
console.log("Successful json")
}).catch(SyntaxError, function (e) {
console.error("file contains invalid json");
}).error(function (e) {
console.error("unable to read file, because: ", e.message);
});
```
Now, because there is no catch-all handler, if you typed `console.lag` (causes an error you don't expect), you will see:
```
Possibly unhandled TypeError: Object #<Console> has no method 'lag'
at application.js:8:13
From previous event:
at Object.<anonymous> (application.js:7:4)
at Module._compile (module.js:449:26)
at Object.Module._extensions..js (module.js:467:10)
at Module.load (module.js:349:32)
at Function.Module._load (module.js:305:12)
at Function.Module.runMain (module.js:490:10)
at startup (node.js:121:16)
at node.js:761:3
```
*( If you don't get the above - you need to enable [long stack traces](#promiselongstacktraces---undefined) )*
And if the file contains invalid JSON:
```
file contains invalid json
```
And if the `fs` module causes an error like file not found:
```
unable to read file, because: ENOENT, open 'not_there.txt'
```
</markdown></div>
<div id="disqus_thread"></div>
<script type="text/javascript">
var disqus_title = ".error";
var disqus_shortname = "bluebirdjs";
var disqus_identifier = "disqus-id-error";
(function() {
var dsq = document.createElement("script"); dsq.type = "text/javascript"; dsq.async = true;
dsq.src = "//" + disqus_shortname + ".disqus.com/embed.js";
(document.getElementsByTagName("head")[0] || document.getElementsByTagName("body")[0]).appendChild(dsq);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript" rel="nofollow">comments powered by Disqus.</a></noscript>

View File

@ -0,0 +1,34 @@
---
layout: api
id: filter
title: .filter
---
[← Back To API Reference](/docs/api-reference.html)
<div class="api-code-section"><markdown>
##.filter
```js
.filter(
function(any item, int index, int length) filterer,
[Object {concurrency: int=Infinity} options]
) -> Promise
```
Same as [Promise.filter(this, filterer, options)](.).
</markdown></div>
<div id="disqus_thread"></div>
<script type="text/javascript">
var disqus_title = ".filter";
var disqus_shortname = "bluebirdjs";
var disqus_identifier = "disqus-id-filter";
(function() {
var dsq = document.createElement("script"); dsq.type = "text/javascript"; dsq.async = true;
dsq.src = "//" + disqus_shortname + ".disqus.com/embed.js";
(document.getElementsByTagName("head")[0] || document.getElementsByTagName("body")[0]).appendChild(dsq);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript" rel="nofollow">comments powered by Disqus.</a></noscript>

View File

@ -0,0 +1,97 @@
---
layout: api
id: finally
title: .finally
---
[← Back To API Reference](/docs/api-reference.html)
<div class="api-code-section"><markdown>
##.finally
```js
.finally(function() handler) -> Promise
```
```js
.lastly(function() handler) -> Promise
```
Pass a handler that will be called regardless of this promise's fate. Returns a new promise chained from this promise. There are special semantics for [`.finally`](.) in that the final value cannot be modified from the handler.
*Note: using [`.finally`](.) for resource management has better alternatives, see [resource management](/docs/api/resource-management.html)*
Consider the example:
```js
function anyway() {
$("#ajax-loader-animation").hide();
}
function ajaxGetAsync(url) {
return new Promise(function (resolve, reject) {
var xhr = new XMLHttpRequest;
xhr.addEventListener("error", reject);
xhr.addEventListener("load", resolve);
xhr.open("GET", url);
xhr.send(null);
}).then(anyway, anyway);
}
```
This example doesn't work as intended because the `then` handler actually swallows the exception and returns `undefined` for any further chainers.
The situation can be fixed with `.finally`:
```js
function ajaxGetAsync(url) {
return new Promise(function (resolve, reject) {
var xhr = new XMLHttpRequest;
xhr.addEventListener("error", reject);
xhr.addEventListener("load", resolve);
xhr.open("GET", url);
xhr.send(null);
}).finally(function() {
$("#ajax-loader-animation").hide();
});
}
```
Now the animation is hidden but, unless it throws an exception, the function has no effect on the fulfilled or rejected value of the returned promise. This is similar to how the synchronous `finally` keyword behaves.
If the handler function passed to `.finally` returns a promise, the promise returned by `.finally` will not be settled until the promise returned by the handler is settled. If the handler fulfills its promise, the returned promise will be fulfilled or rejected with the original value. If the handler rejects its promise, the returned promise will be rejected with the handler's value. This is similar to throwing an exception in a synchronous `finally` block, causing the original value or exception to be forgotten. This delay can be useful if the actions performed by the handler are done asynchronously. For example:
```js
function ajaxGetAsync(url) {
return new Promise(function (resolve, reject) {
var xhr = new XMLHttpRequest;
xhr.addEventListener("error", reject);
xhr.addEventListener("load", resolve);
xhr.open("GET", url);
xhr.send(null);
}).finally(function() {
return Promise.fromCallback(function(callback) {
$("#ajax-loader-animation").fadeOut(1000, callback);
});
});
}
```
If the fade out completes successfully, the returned promise will be fulfilled or rejected with the value from `xhr`. If `.fadeOut` throws an exception or passes an error to the callback, the returned promise will be rejected with the error from `.fadeOut`.
*For compatibility with earlier ECMAScript version, an alias `.lastly` is provided for [`.finally`](.).*
</markdown></div>
<div id="disqus_thread"></div>
<script type="text/javascript">
var disqus_title = ".finally";
var disqus_shortname = "bluebirdjs";
var disqus_identifier = "disqus-id-finally";
(function() {
var dsq = document.createElement("script"); dsq.type = "text/javascript"; dsq.async = true;
dsq.src = "//" + disqus_shortname + ".disqus.com/embed.js";
(document.getElementsByTagName("head")[0] || document.getElementsByTagName("body")[0]).appendChild(dsq);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript" rel="nofollow">comments powered by Disqus.</a></noscript>

View File

@ -0,0 +1,27 @@
---
layout: api
id: generators
title: Generators
---
[← Back To API Reference](/docs/api-reference.html)
<div class="api-code-section"><markdown>
##Generators
Using ECMAScript6 generators feature to implement C# 5.0 `async/await` like syntax.
</markdown></div>
<div id="disqus_thread"></div>
<script type="text/javascript">
var disqus_title = "Generators";
var disqus_shortname = "bluebirdjs";
var disqus_identifier = "disqus-id-generators";
(function() {
var dsq = document.createElement("script"); dsq.type = "text/javascript"; dsq.async = true;
dsq.src = "//" + disqus_shortname + ".disqus.com/embed.js";
(document.getElementsByTagName("head")[0] || document.getElementsByTagName("body")[0]).appendChild(dsq);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript" rel="nofollow">comments powered by Disqus.</a></noscript>

View File

@ -0,0 +1,59 @@
---
layout: api
id: get
title: .get
---
[← Back To API Reference](/docs/api-reference.html)
<div class="api-code-section"><markdown>
##.get
```js
.get(String propertyName|int index) -> Promise
```
This is a convenience method for doing:
```js
promise.then(function(obj) {
return obj[propertyName];
});
```
For example:
```js
db.query("...")
.get(0)
.then(function(firstRow) {
});
```
If `index` is negative, the indexed load will become `obj.length + index`. So that -1 can be used to read last item
in the array, -2 to read the second last and so on. For example:
```js
Promise.resolve([1,2,3]).get(-1).then(function(value) {
console.log(value); // 3
});
```
If the `index` is still negative after `obj.length + index`, it will be clamped to 0.
</markdown></div>
<div id="disqus_thread"></div>
<script type="text/javascript">
var disqus_title = ".get";
var disqus_shortname = "bluebirdjs";
var disqus_identifier = "disqus-id-get";
(function() {
var dsq = document.createElement("script"); dsq.type = "text/javascript"; dsq.async = true;
dsq.src = "//" + disqus_shortname + ".disqus.com/embed.js";
(document.getElementsByTagName("head")[0] || document.getElementsByTagName("body")[0]).appendChild(dsq);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript" rel="nofollow">comments powered by Disqus.</a></noscript>

View File

@ -0,0 +1,31 @@
---
layout: api
id: iscancelled
title: .isCancelled
---
[← Back To API Reference](/docs/api-reference.html)
<div class="api-code-section"><markdown>
##.isCancelled
```js
.isCancelled() -> boolean
```
See if this `promise` has been cancelled.
</markdown></div>
<div id="disqus_thread"></div>
<script type="text/javascript">
var disqus_title = ".isCancelled";
var disqus_shortname = "bluebirdjs";
var disqus_identifier = "disqus-id-iscancelled";
(function() {
var dsq = document.createElement("script"); dsq.type = "text/javascript"; dsq.async = true;
dsq.src = "//" + disqus_shortname + ".disqus.com/embed.js";
(document.getElementsByTagName("head")[0] || document.getElementsByTagName("body")[0]).appendChild(dsq);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript" rel="nofollow">comments powered by Disqus.</a></noscript>

View File

@ -0,0 +1,31 @@
---
layout: api
id: isfulfilled
title: .isFulfilled
---
[← Back To API Reference](/docs/api-reference.html)
<div class="api-code-section"><markdown>
##.isFulfilled
```js
.isFulfilled() -> boolean
```
See if this `promise` has been fulfilled.
</markdown></div>
<div id="disqus_thread"></div>
<script type="text/javascript">
var disqus_title = ".isFulfilled";
var disqus_shortname = "bluebirdjs";
var disqus_identifier = "disqus-id-isfulfilled";
(function() {
var dsq = document.createElement("script"); dsq.type = "text/javascript"; dsq.async = true;
dsq.src = "//" + disqus_shortname + ".disqus.com/embed.js";
(document.getElementsByTagName("head")[0] || document.getElementsByTagName("body")[0]).appendChild(dsq);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript" rel="nofollow">comments powered by Disqus.</a></noscript>

View File

@ -0,0 +1,32 @@
---
layout: api
id: ispending
title: .isPending
---
[← Back To API Reference](/docs/api-reference.html)
<div class="api-code-section"><markdown>
##.isPending
```js
.isPending() -> boolean
```
See if this `promise` is pending (not fulfilled or rejected or cancelled).
</markdown></div>
<div id="disqus_thread"></div>
<script type="text/javascript">
var disqus_title = ".isPending";
var disqus_shortname = "bluebirdjs";
var disqus_identifier = "disqus-id-ispending";
(function() {
var dsq = document.createElement("script"); dsq.type = "text/javascript"; dsq.async = true;
dsq.src = "//" + disqus_shortname + ".disqus.com/embed.js";
(document.getElementsByTagName("head")[0] || document.getElementsByTagName("body")[0]).appendChild(dsq);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript" rel="nofollow">comments powered by Disqus.</a></noscript>

View File

@ -0,0 +1,31 @@
---
layout: api
id: isrejected
title: .isRejected
---
[← Back To API Reference](/docs/api-reference.html)
<div class="api-code-section"><markdown>
##.isRejected
```js
.isRejected() -> boolean
```
See if this `promise` has been rejected.
</markdown></div>
<div id="disqus_thread"></div>
<script type="text/javascript">
var disqus_title = ".isRejected";
var disqus_shortname = "bluebirdjs";
var disqus_identifier = "disqus-id-isrejected";
(function() {
var dsq = document.createElement("script"); dsq.type = "text/javascript"; dsq.async = true;
dsq.src = "//" + disqus_shortname + ".disqus.com/embed.js";
(document.getElementsByTagName("head")[0] || document.getElementsByTagName("body")[0]).appendChild(dsq);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript" rel="nofollow">comments powered by Disqus.</a></noscript>

View File

@ -0,0 +1,34 @@
---
layout: api
id: map
title: .map
---
[← Back To API Reference](/docs/api-reference.html)
<div class="api-code-section"><markdown>
##.map
```js
.map(
function(any item, int index, int length) mapper,
[Object {concurrency: int=Infinity} options]
) -> Promise
```
Same as [Promise.map(this, mapper, options)](.).
</markdown></div>
<div id="disqus_thread"></div>
<script type="text/javascript">
var disqus_title = ".map";
var disqus_shortname = "bluebirdjs";
var disqus_identifier = "disqus-id-map";
(function() {
var dsq = document.createElement("script"); dsq.type = "text/javascript"; dsq.async = true;
dsq.src = "//" + disqus_shortname + ".disqus.com/embed.js";
(document.getElementsByTagName("head")[0] || document.getElementsByTagName("body")[0]).appendChild(dsq);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript" rel="nofollow">comments powered by Disqus.</a></noscript>

View File

@ -0,0 +1,31 @@
---
layout: api
id: mapseries
title: .mapseries
---
[← Back To API Reference](/docs/api-reference.html)
<div class="api-code-section"><markdown>
##.mapSeries
```js
.mapSeries(function(any item, int index, int length) mapper) -> Promise
```
Same as [Promise.mapSeries(this, iterator)](.).
</markdown></div>
<div id="disqus_thread"></div>
<script type="text/javascript">
var disqus_title = ".mapSeries";
var disqus_shortname = "bluebirdjs";
var disqus_identifier = "disqus-id-mapSeries";
(function() {
var dsq = document.createElement("script"); dsq.type = "text/javascript"; dsq.async = true;
dsq.src = "//" + disqus_shortname + ".disqus.com/embed.js";
(document.getElementsByTagName("head")[0] || document.getElementsByTagName("body")[0]).appendChild(dsq);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript" rel="nofollow">comments powered by Disqus.</a></noscript>

View File

@ -0,0 +1,79 @@
---
layout: api
id: new-promise
title: new Promise
---
[← Back To API Reference](/docs/api-reference.html)
<div class="api-code-section"><markdown>
##new Promise
```js
new Promise(function(function resolve, function reject) resolver) -> Promise
```
Create a new promise. The passed in function will receive functions `resolve` and `reject` as its arguments which can be called to seal the fate of the created promise.
*Note: See [explicit construction anti-pattern]({{ "/docs/anti-patterns.html#the-explicit-construction-anti-pattern" | prepend: site.baseurl }}) before creating promises yourself*
Example:
```js
function ajaxGetAsync(url) {
return new Promise(function (resolve, reject) {
var xhr = new XMLHttpRequest;
xhr.addEventListener("error", reject);
xhr.addEventListener("load", resolve);
xhr.open("GET", url);
xhr.send(null);
});
}
```
If you pass a promise object to the `resolve` function, the created promise will follow the state of that promise.
<hr>
To make sure a function that returns a promise is following the implicit but critically important contract of promises, you can start a function with `new Promise` if you cannot start a chain immediately:
```js
function getConnection(urlString) {
return new Promise(function(resolve) {
//Without new Promise, this throwing will throw an actual exception
var params = parse(urlString);
resolve(getAdapter(params).getConnection());
});
}
```
The above ensures `getConnection` fulfills the contract of a promise-returning function of never throwing a synchronous exception. Also see [`Promise.try`](.) and [`Promise.method`](.)
The resolver is called synchronously (the following is for documentation purposes and not idiomatic code):
```js
function getPromiseResolveFn() {
var res;
new Promise(function (resolve) {
res = resolve;
});
// res is guaranteed to be set
return res;
}
```
</markdown></div>
<div id="disqus_thread"></div>
<script type="text/javascript">
var disqus_title = "new Promise";
var disqus_shortname = "bluebirdjs";
var disqus_identifier = "disqus-id-new-promise";
(function() {
var dsq = document.createElement("script"); dsq.type = "text/javascript"; dsq.async = true;
dsq.src = "//" + disqus_shortname + ".disqus.com/embed.js";
(document.getElementsByTagName("head")[0] || document.getElementsByTagName("body")[0]).appendChild(dsq);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript" rel="nofollow">comments powered by Disqus.</a></noscript>

View File

@ -0,0 +1,35 @@
---
layout: api
id: operationalerror
title: OperationalError
---
[← Back To API Reference](/docs/api-reference.html)
<div class="api-code-section"><markdown>
##OperationalError
```js
new OperationalError(String message) -> OperationalError
```
Represents an error is an explicit promise rejection as opposed to a thrown error. For example, if an error is errbacked by a callback API promisified through undefined or undefined
and is not a typed error, it will be converted to a `OperationalError` which has the original error in the `.cause` property.
`OperationalError`s are caught in [`.error`](.) handlers.
</markdown></div>
<div id="disqus_thread"></div>
<script type="text/javascript">
var disqus_title = "OperationalError";
var disqus_shortname = "bluebirdjs";
var disqus_identifier = "disqus-id-operationalerror";
(function() {
var dsq = document.createElement("script"); dsq.type = "text/javascript"; dsq.async = true;
dsq.src = "//" + disqus_shortname + ".disqus.com/embed.js";
(document.getElementsByTagName("head")[0] || document.getElementsByTagName("body")[0]).appendChild(dsq);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript" rel="nofollow">comments powered by Disqus.</a></noscript>

View File

@ -0,0 +1,105 @@
---
layout: api
id: progression-migration
title: Progression migration
---
[← Back To API Reference](/docs/api-reference.html)
<div class="api-code-section"><markdown>
##Progression migration
Progression has been removed as there are composability and chaining issues with APIs that use promise progression handlers. Implementing the common use case of progress bars can be accomplished using a pattern similar to [IProgress](http://blogs.msdn.com/b/dotnet/archive/2012/06/06/async-in-4-5-enabling-progress-and-cancellation-in-async-apis.aspx) in C#.
For old code that still uses it, see [the progression docs in the deprecated API documentation](/docs/deprecated-apis.html#progression).
Using jQuery before:
```js
Promise.resolve($.get(...))
.progressed(function() {
// ...
})
.then(function() {
// ...
})
.catch(function(e) {
// ...
})
```
Using jQuery after:
```js
Promise.resolve($.get(...).progress(function() {
// ...
}))
.then(function() {
// ...
})
.catch(function(e) {
// ...
})
```
Implementing general progress interfaces like in C#:
```js
function returnsPromiseWithProgress(progressHandler) {
return doFirstAction().tap(function() {
progressHandler(0.33);
}).then(doSecondAction).tap(function() {
progressHandler(0.66);
}).then(doThirdAction).tap(function() {
progressHandler(1.00);
});
}
returnsPromiseWithProgress(function(progress) {
ui.progressbar.setWidth((progress * 200) + "px"); // update with on client side
}).then(function(value) { // action complete
// entire chain is complete.
}).catch(function(e) {
// error
});
```
Another example using `coroutine`:
```js
var doNothing = function() {};
var progressSupportingCoroutine = Promise.coroutine(function* (progress) {
progress = typeof progress === "function" ? progress : doNothing;
var first = yield getFirstValue();
// 33% done
progress(0.33);
var second = yield getSecondValue();
progress(0.67);
var third = yield getThirdValue();
progress(1);
return [first, second, third];
});
var progressConsumingCoroutine = Promise.coroutine(function* () {
var allValues = yield progressSupportingCoroutine(function(p) {
ui.progressbar.setWidth((p * 200) + "px");
});
var second = allValues[1];
// ...
});
```
</markdown></div>
<div id="disqus_thread"></div>
<script type="text/javascript">
var disqus_title = "Progression migration";
var disqus_shortname = "bluebirdjs";
var disqus_identifier = "disqus-id-progression-migration";
(function() {
var dsq = document.createElement("script"); dsq.type = "text/javascript"; dsq.async = true;
dsq.src = "//" + disqus_shortname + ".disqus.com/embed.js";
(document.getElementsByTagName("head")[0] || document.getElementsByTagName("body")[0]).appendChild(dsq);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript" rel="nofollow">comments powered by Disqus.</a></noscript>

View File

@ -0,0 +1,47 @@
---
layout: api
id: promise.all
title: Promise.all
---
[← Back To API Reference](/docs/api-reference.html)
<div class="api-code-section"><markdown>
##Promise.all
```js
Promise.all(Iterable<any>|Promise<Iterable<any>> input) -> Promise
```
This method is useful for when you want to wait for more than one promise to complete.
Given an [`Iterable`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols)\(arrays are `Iterable`\), or a promise of an `Iterable`, which produces promises (or a mix of promises and values), iterate over all the values in the `Iterable` into an array and return a promise that is fulfilled when all the items in the array are fulfilled. The promise's fulfillment value is an array with fulfillment values at respective positions to the original array. If any promise in the array rejects, the returned promise is rejected with the rejection reason.
```js
var files = [];
for (var i = 0; i < 100; ++i) {
files.push(fs.writeFileAsync("file-" + i + ".txt", "", "utf-8"));
}
Promise.all(files).then(function() {
console.log("all the files were created");
});
```
This method is compatible with [`Promise.all`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all) from native promises.
</markdown></div>
<div id="disqus_thread"></div>
<script type="text/javascript">
var disqus_title = "Promise.all";
var disqus_shortname = "bluebirdjs";
var disqus_identifier = "disqus-id-promise.all";
(function() {
var dsq = document.createElement("script"); dsq.type = "text/javascript"; dsq.async = true;
dsq.src = "//" + disqus_shortname + ".disqus.com/embed.js";
(document.getElementsByTagName("head")[0] || document.getElementsByTagName("body")[0]).appendChild(dsq);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript" rel="nofollow">comments powered by Disqus.</a></noscript>

View File

@ -0,0 +1,31 @@
---
layout: api
id: promise.any
title: Promise.any
---
[← Back To API Reference](/docs/api-reference.html)
<div class="api-code-section"><markdown>
##Promise.any
```js
Promise.any(Iterable<any>|Promise<Iterable<any>> input) -> Promise
```
Like [Promise.some](.), with 1 as `count`. However, if the promise fulfills, the fulfillment value is not an array of 1 but the value directly.
</markdown></div>
<div id="disqus_thread"></div>
<script type="text/javascript">
var disqus_title = "Promise.any";
var disqus_shortname = "bluebirdjs";
var disqus_identifier = "disqus-id-promise.any";
(function() {
var dsq = document.createElement("script"); dsq.type = "text/javascript"; dsq.async = true;
dsq.src = "//" + disqus_shortname + ".disqus.com/embed.js";
(document.getElementsByTagName("head")[0] || document.getElementsByTagName("body")[0]).appendChild(dsq);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript" rel="nofollow">comments powered by Disqus.</a></noscript>

View File

@ -0,0 +1,177 @@
---
layout: api
id: promise.bind
title: Promise.bind
---
[← Back To API Reference](/docs/api-reference.html)
<div class="api-code-section"><markdown>
##Promise.bind
```js
Promise.bind(
any|Promise<any> thisArg,
[any|Promise<any> value=undefined]
) -> BoundPromise
```
Create a promise that follows this promise or in the static method is resolved with the given `value`, but is bound to the given `thisArg` value. A bound promise will call its handlers with the bound value set to `this`. Additionally promises derived from a bound promise will also be bound promises with the same `thisArg` binding as the original promise.
If `thisArg` is a promise or thenable, its resolution will be awaited for and the bound value will be the promise's fulfillment value. If `thisArg` rejects
then the returned promise is rejected with the `thisArg's` rejection reason. Note that this means you cannot use `this` without checking inside catch handlers for promises that bind to promise because in case of rejection of `thisArg`, `this` will be `undefined`.
<hr>
Without arrow functions that provide lexical `this`, the correspondence between async and sync code breaks down when writing object-oriented code. [`.bind`](.) alleviates this.
Consider:
```js
MyClass.prototype.method = function() {
try {
var contents = fs.readFileSync(this.file);
var url = urlParse(contents);
var result = this.httpGetSync(url);
var refined = this.refine(result);
return this.writeRefinedSync(refined);
}
catch (e) {
this.error(e.stack);
}
};
```
The above has a direct translation:
```js
MyClass.prototype.method = function() {
return fs.readFileAsync(this.file).bind(this)
.then(function(contents) {
var url = urlParse(contents);
return this.httpGetAsync(url);
}).then(function(result) {
var refined = this.refine(result);
return this.writeRefinedAsync(refined);
}).catch(function(e) {
this.error(e.stack);
});
};
```
`.bind` is the most efficient way of utilizing `this` with promises. The handler functions in the above code are not closures and can therefore even be hoisted out if needed. There is literally no overhead when propagating the bound value from one promise to another.
<hr>
`.bind` also has a useful side purpose - promise handlers don't need to share a function to use shared state:
```js
somethingAsync().bind({})
.spread(function (aValue, bValue) {
this.aValue = aValue;
this.bValue = bValue;
return somethingElseAsync(aValue, bValue);
})
.then(function (cValue) {
return this.aValue + this.bValue + cValue;
});
```
The above without [`.bind`](.) could be achieved with:
```js
var scope = {};
somethingAsync()
.spread(function (aValue, bValue) {
scope.aValue = aValue;
scope.bValue = bValue;
return somethingElseAsync(aValue, bValue);
})
.then(function (cValue) {
return scope.aValue + scope.bValue + cValue;
});
```
However, there are many differences when you look closer:
- Requires a statement so cannot be used in an expression context
- If not there already, an additional wrapper function is required to aundefined leaking or sharing `scope`
- The handler functions are now closures, thus less efficient and not reusable
<hr>
Note that bind is only propagated with promise transformation. If you create new promise chains inside a handler, those chains are not bound to the "upper" `this`:
```js
something().bind(var1).then(function() {
//`this` is var1 here
return Promise.all(getStuff()).then(function(results) {
//`this` is undefined here
//refine results here etc
});
}).then(function() {
//`this` is var1 here
});
```
However, if you are utilizing the full bluebird API offering, you will *almost never* need to resort to nesting promises in the first place. The above should be written more like:
```js
something().bind(var1).then(function() {
//`this` is var1 here
return getStuff();
}).map(function(result) {
//`this` is var1 here
//refine result here
}).then(function() {
//`this` is var1 here
});
```
Also see this [Stackoverflow answer](http://stackoverflow.com/a/24412873/191693) as an additional example.
<hr>
If you don't want to return a bound promise to the consumers of a promise, you can rebind the chain at the end:
```js
MyClass.prototype.method = function() {
return fs.readFileAsync(this.file).bind(this)
.then(function(contents) {
var url = urlParse(contents);
return this.httpGetAsync(url);
}).then(function(result) {
var refined = this.refine(result);
return this.writeRefinedAsync(refined);
}).catch(function(e) {
this.error(e.stack);
}).bind(); //The `thisArg` is implicitly undefined - I.E. the default promise `this` value
};
```
Rebinding can also be abused to do something gratuitous like this:
```js
Promise.resolve("my-element")
.bind(document)
.then(document.getElementById)
.bind(console)
.then(console.log);
```
The above does a `console.log` of `my-element`. Doing it this way is necessary because neither of the methods (`getElementById`, `console.log`) can be called as stand-alone methods.
</markdown></div>
<div id="disqus_thread"></div>
<script type="text/javascript">
var disqus_title = "Promise.bind";
var disqus_shortname = "bluebirdjs";
var disqus_identifier = "disqus-id-promise.bind";
(function() {
var dsq = document.createElement("script"); dsq.type = "text/javascript"; dsq.async = true;
dsq.src = "//" + disqus_shortname + ".disqus.com/embed.js";
(document.getElementsByTagName("head")[0] || document.getElementsByTagName("body")[0]).appendChild(dsq);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript" rel="nofollow">comments powered by Disqus.</a></noscript>

View File

@ -0,0 +1,84 @@
---
layout: api
id: promise.config
title: Promise.config
---
[← Back To API Reference](/docs/api-reference.html)
<div class="api-code-section"><markdown>
##Promise.config
```js
Promise.config(Object {
warnings: boolean=false,
longStackTraces: boolean=false,
cancellation: boolean=false,
monitoring: boolean=false
} options) -> undefined;
```
Configure long stack traces, warnings, monitoring and cancellation. Note that even though `false` is the default here, a development environment might be detected which automatically enables long stack traces and warnings.
```js
Promise.config({
// Enable warnings
warnings: true,
// Enable long stack traces
longStackTraces: true,
// Enable cancellation
cancellation: true,
// Enable monitoring
monitoring: true
});
```
You can configure the warning for checking forgotten return statements with `wForgottenReturn`:
```js
Promise.config({
// Enables all warnings except forgotten return statements.
warnings: {
wForgottenReturn: false
}
});
```
`wForgottenReturn` is the only warning type that can be separately configured. The corresponding environmental variable key is `BLUEBIRD_W_FORGOTTEN_RETURN`.
<hr>
In Node.js you may configure warnings and long stack traces for the entire process using environment variables:
```
BLUEBIRD_LONG_STACK_TRACES=1 BLUEBIRD_WARNINGS=1 node app.js
```
Both features are automatically enabled if the `BLUEBIRD_DEBUG` environment variable has been set or if the `NODE_ENV` environment variable is equal to `"development"`.
Using the value `0` will explicitly disable a feature despite debug environment otherwise activating it:
```
# Warnings are disabled despite being in development environment
NODE_ENV=development BLUEBIRD_WARNINGS=0 node app.js
```
Cancellation is always configured separately per bluebird instance.
</markdown></div>
<div id="disqus_thread"></div>
<script type="text/javascript">
var disqus_title = "Promise.config";
var disqus_shortname = "bluebirdjs";
var disqus_identifier = "disqus-id-promise.config";
(function() {
var dsq = document.createElement("script"); dsq.type = "text/javascript"; dsq.async = true;
dsq.src = "//" + disqus_shortname + ".disqus.com/embed.js";
(document.getElementsByTagName("head")[0] || document.getElementsByTagName("body")[0]).appendChild(dsq);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript" rel="nofollow">comments powered by Disqus.</a></noscript>

View File

@ -0,0 +1,122 @@
---
layout: api
id: promise.coroutine.addyieldhandler
title: Promise.coroutine.addYieldHandler
---
[← Back To API Reference](/docs/api-reference.html)
<div class="api-code-section"><markdown>
##Promise.coroutine.addYieldHandler
```js
Promise.coroutine.addYieldHandler(function handler) -> undefined
```
By default you can only yield Promises and Thenables inside coroutines. You can use this function to add yielding support for arbitrary types.
For example, if you wanted `yield 500` to be same as `yield Promise.delay`:
```js
Promise.coroutine.addYieldHandler(function(value) {
if (typeof value === "number") return Promise.delay(value);
});
```
Yield handlers are called when you yield something that is not supported by default. The first yield handler to return a promise or a thenable will be used.
If no yield handler returns a promise or a thenable then an error is raised.
An example of implementing callback support with `addYieldHandler`:
*This is a demonstration of how powerful the feature is and not the recommended usage. For best performance you need to use `promisifyAll` and yield promises directly.*
```js
var Promise = require("bluebird");
var fs = require("fs");
var _ = (function() {
var promise = null;
Promise.coroutine.addYieldHandler(function(v) {
if (v === undefined && promise != null) {
return promise;
}
promise = null;
});
return function() {
var def = Promise.defer();
promise = def.promise;
return def.callback;
};
})();
var readFileJSON = Promise.coroutine(function* (fileName) {
var contents = yield fs.readFile(fileName, "utf8", _());
return JSON.parse(contents);
});
```
An example of implementing thunks support with `addYieldHandler`:
*This is a demonstration of how powerful the feature is and not the recommended usage. For best performance you need to use `promisifyAll` and yield promises directly.*
```js
var Promise = require("bluebird");
var fs = require("fs");
Promise.coroutine.addYieldHandler(function(v) {
if (typeof v === "function") {
return Promise.fromCallback(function(cb) {
v(cb);
});
}
});
var readFileThunk = function(fileName, encoding) {
return function(cb) {
return fs.readFile(fileName, encoding, cb);
};
};
var readFileJSON = Promise.coroutine(function* (fileName) {
var contents = yield readFileThunk(fileName, "utf8");
return JSON.parse(contents);
});
```
An example of handling promises in parallel by adding an `addYieldHandler` for arrays :
```js
var Promise = require("bluebird");
var fs = Promise.promisifyAll(require("fs"));
Promise.coroutine.addYieldHandler(function(yieldedValue) {
if (Array.isArray(yieldedValue)) return Promise.all(yieldedValue);
});
var readFiles = Promise.coroutine(function* (fileNames) {
var promises = [];
fileNames.forEach(function (fileName) {
promises.push(fs.readFileAsync(fileName, "utf8"));
});
return yield promises;
});
```
</markdown></div>
<div id="disqus_thread"></div>
<script type="text/javascript">
var disqus_title = "Promise.coroutine.addYieldHandler";
var disqus_shortname = "bluebirdjs";
var disqus_identifier = "disqus-id-promise.coroutine.addyieldhandler";
(function() {
var dsq = document.createElement("script"); dsq.type = "text/javascript"; dsq.async = true;
dsq.src = "//" + disqus_shortname + ".disqus.com/embed.js";
(document.getElementsByTagName("head")[0] || document.getElementsByTagName("body")[0]).appendChild(dsq);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript" rel="nofollow">comments powered by Disqus.</a></noscript>

View File

@ -0,0 +1,76 @@
---
layout: api
id: promise.coroutine
title: Promise.coroutine
---
[← Back To API Reference](/docs/api-reference.html)
<div class="api-code-section"><markdown>
##Promise.coroutine
```js
Promise.coroutine(GeneratorFunction(...arguments) generatorFunction) -> function
```
Returns a function that can use `yield` to yield promises. Control is returned back to the generator when the yielded promise settles. This can lead to less verbose code when doing lots of sequential async calls with minimal processing in between. Requires node.js 0.12+, io.js 1.0+ or Google Chrome 40+.
```js
var Promise = require("bluebird");
function PingPong() {
}
PingPong.prototype.ping = Promise.coroutine(function* (val) {
console.log("Ping?", val)
yield Promise.delay(500)
this.pong(val+1)
});
PingPong.prototype.pong = Promise.coroutine(function* (val) {
console.log("Pong!", val)
yield Promise.delay(500);
this.ping(val+1)
});
var a = new PingPong();
a.ping(0);
```
Running the example:
$ node test.js
Ping? 0
Pong! 1
Ping? 2
Pong! 3
Ping? 4
Pong! 5
Ping? 6
Pong! 7
Ping? 8
...
When called, the coroutine function will start an instance of the generator and returns a promise for its final value.
Doing `Promise.coroutine` is almost like using the C# `async` keyword to mark the function, with `yield` working as the `await` keyword. Promises are somewhat like `Task`s.
**Tip**
You are able to yield non-promise values by adding your own yield handler using [`Promise.coroutine.addYieldHandler`](.)
</markdown></div>
<div id="disqus_thread"></div>
<script type="text/javascript">
var disqus_title = "Promise.coroutine";
var disqus_shortname = "bluebirdjs";
var disqus_identifier = "disqus-id-promise.coroutine";
(function() {
var dsq = document.createElement("script"); dsq.type = "text/javascript"; dsq.async = true;
dsq.src = "//" + disqus_shortname + ".disqus.com/embed.js";
(document.getElementsByTagName("head")[0] || document.getElementsByTagName("body")[0]).appendChild(dsq);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript" rel="nofollow">comments powered by Disqus.</a></noscript>

View File

@ -0,0 +1,47 @@
---
layout: api
id: promise.delay
title: Promise.delay
---
[← Back To API Reference](/docs/api-reference.html)
<div class="api-code-section"><markdown>
##Promise.delay
```js
Promise.delay(
int ms,
[any|Promise<any> value=undefined]
) -> Promise
```
Returns a promise that will be resolved with `value` (or `undefined`) after given `ms` milliseconds. If `value` is a promise, the delay will start counting down when it is fulfilled and the returned promise will be fulfilled with the fulfillment value of the `value` promise.
```js
Promise.delay(500).then(function() {
console.log("500 ms passed");
return "Hello world";
}).delay(500).then(function(helloWorldString) {
console.log(helloWorldString);
console.log("another 500 ms passed") ;
});
```
<hr>
</markdown></div>
<div id="disqus_thread"></div>
<script type="text/javascript">
var disqus_title = "Promise.delay";
var disqus_shortname = "bluebirdjs";
var disqus_identifier = "disqus-id-promise.delay";
(function() {
var dsq = document.createElement("script"); dsq.type = "text/javascript"; dsq.async = true;
dsq.src = "//" + disqus_shortname + ".disqus.com/embed.js";
(document.getElementsByTagName("head")[0] || document.getElementsByTagName("body")[0]).appendChild(dsq);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript" rel="nofollow">comments powered by Disqus.</a></noscript>

View File

@ -0,0 +1,42 @@
---
layout: api
id: promise.each
title: Promise.each
---
[← Back To API Reference](/docs/api-reference.html)
<div class="api-code-section"><markdown>
##Promise.each
```js
Promise.each(
Iterable<any>|Promise<Iterable<any>> input,
function(any item, int index, int length) iterator
) -> Promise
```
[api/promise.each](unfinished-article)
Iterate over an array, or a promise of an array, which contains promises (or a mix of promises and values) with the given `iterator` function with the signature `(value, index, length)` where `value` is the resolved value of a respective promise in the input array. Iteration happens serially. If any promise in the input array is rejected the returned promise is rejected as well.
Resolves to the original array unmodified, this method is meant to be used for side effects. If the iterator function returns a promise or a thenable, then the result of the promise is awaited, before continuing with next iteration.
<hr>
</markdown></div>
<div id="disqus_thread"></div>
<script type="text/javascript">
var disqus_title = "Promise.each";
var disqus_shortname = "bluebirdjs";
var disqus_identifier = "disqus-id-promise.each";
(function() {
var dsq = document.createElement("script"); dsq.type = "text/javascript"; dsq.async = true;
dsq.src = "//" + disqus_shortname + ".disqus.com/embed.js";
(document.getElementsByTagName("head")[0] || document.getElementsByTagName("body")[0]).appendChild(dsq);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript" rel="nofollow">comments powered by Disqus.</a></noscript>

Some files were not shown because too many files have changed in this diff Show More