Motivation

Consider the following synchronous JavaScript function to read a file and parse it as JSON. It is simple and easy to read, but you wouldn't want to use it in most applications as it is blocking. This means that while you are reading the file from disk (a slow operation) nothing else can happen.

function readJSONSync(filename) {
return JSON.parse(fs.readFileSync(filename, 'utf8'));
}

To make our application performant and responsive, we need to make all the operations that involve IO be asynchronous. The simplest way to do this would be to use a callback. However, a naive implementation will probably go wrong:

function readJSON(filename, callback){
fs.readFile(filename, 'utf8', function (err, res){
if (err) return callback(err);
callback(null, JSON.parse(res));
});
}
  • The extra callback parameter confuses our idea of what is input and what is the return value.
  • It doesn't work at all with control flow primitives.
  • It doesn't handle errors thrown by JSON.parse

We need to handle errors thrown by JSON.parse but we also need to be careful not to handle errors thrown by the callback function. By the time we've done all of this our code is a mess of error handling:

function readJSON(filename, callback){
fs.readFile(filename, 'utf8', function (err, res){
if (err) return callback(err);
try {
res = JSON.parse(res);
} catch (ex) {
return callback(ex);
}
callback(null, res);
});
}

Despite all this mess of error handling code, we are still left with the problem of the extra callback parameter hanging around. Promises help you naturally handle errors, and write cleaner code by not having callback parameters, and without modifying the underlying architecture (i.e. you can implement them in pure JavaScript and use them to wrap existing asynchronous operations).

What is a promise?

The core idea behind promises is that a promise represents the result of an asynchronous operation. A promise is in one of three different states:

  • pending - The initial state of a promise.
  • fulfilled - The state of a promise representing a successful operation.
  • rejected - The state of a promise representing a failed operation.

Once a promise is fulfilled or rejected, it is immutable (i.e. it can never change again).

Constructing a promise

Once all of the APIs return promises, it should be relatively rare that you need to construct one by hand. In the meantime, we need a way to polyfill existing APIs. For example:

function readFile(filename, enc){
return new Promise(function (fulfill, reject){
fs.readFile(filename, enc, function (err, res){
if (err) reject(err);
else fulfill(res);
});
});
}

We use new Promise to construct the promise. We give the constructor a factory function which does the actual work. This function is called immediately with two arguments. The first argument fulfills the promise and the second argument rejects the promise. Once the operation has completed, we call the appropriate function.

Awaiting a promise

In order to use a promise, we must somehow be able to wait for it to be fulfilled or rejected. The way to do this is using promise.done (see warning at the end of this section if attempting to run these samples).

With this in mind, it's easy to re-write our earlier readJSON function to use promises:

function readJSON(filename){
return new Promise(function (fulfill, reject){
readFile(filename, 'utf8').done(function (res){
try {
fulfill(JSON.parse(res));
} catch (ex) {
reject(ex);
}
}, reject);
});
}

This still has lots of error handling code (we'll see how we can improve on that in the next section) but it's a lot less error prone to write, and we no longer have a strange extra parameter.

Non Standard

Note that promise.done (used in the examples in this section) has not been standardised. It is supported by most major promise libraries though, and is useful both as a teaching aid and in production code. I recommend using it along with the following polyfill (minified / unminified): undefined

Transformation / Chaining

Following our example through, what we really want to do is transform the promise via another operation. In our case, this second operation is synchronous, but it might just as easily have been an asynchronous operation. Fortunately, promises have a (fully standardised, except jQuery) method for transforming promises and chaining operations.

Put simply, .then is to .done as .map is to .forEach. To put that another way, use .then whenever you're going to do something with the result (even if that's just waiting for it to finish) and use .done whenever you aren't planning on doing anything with the result.

Now we can re-write our original example as simply:

function readJSON(filename){
return readFile(filename, 'utf8').then(function (res){
return JSON.parse(res)
})
}

Since JSON.parse is just a function, we could re-write this as:

function readJSON(filename){
return readFile(filename, 'utf8').then(JSON.parse);
}

This is very close to the simple synchronous example we started out with.

Implementations / Polyfills

p Promises are useful both in node.js and the browser

jQuery

This feels like a good time to warn you that what jQuery calls a promise is in fact totally different to what everyone else calls a promise. jQuery's promises have a poorly thought out API that will likely just confuse you. Fortunately, instead of using jQuery's strange version of a promise, you can just convert it to a really simple standardised promise:

var jQueryPromise = $.ajax('/data.json');
var realPromise = Promise.resolve(jQueryPromise);
//now just use `realPromise` however you like.

Browser

Promises are currently only supported by a pretty small selection of browsers (see kangax compatibility tables). The good news is that they're extremely easy to polyfill (minified / unminified):

<script src="https://www.promisejs.org/polyfills/promise-7.0.4.min.js"></script>

None of the browsers currently support Promise.prototype.done so if you want to use that feature, and you are not including the polyfill above, you must at least include this polyfill (minified / unminified):

<script src="https://www.promisejs.org/polyfills/promise-done-7.0.4.min.js"></script>

Node.js

It's generally not seen as good practice to polyfill things in node.js. Instead, you're better off just requiring the library wherever you need it.

To install promise run:

npm install promise --save

Then you can load it into a local variable using require

var Promise = require('promise');

The "promise" library also provides a couple of really useful extensions for interacting with node.js

var readFile = Promise.denodeify(require('fs').readFile);
// now `readFile` will return a promise rather than
// expecting a callback function readJSON(filename, callback){
// If a callback is provided, call it with error as the
// first argument and result as the second argument,
// then return `undefined`. If no callback is provided,
// just return the promise.
return readFile(filename, 'utf8')
.then(JSON.parse)
.nodeify(callback);
}

Further Reading

Promise deeply lookup的更多相关文章

  1. Promise & Deferred Objects in JavaScript Pt.2: in Practice

    原文:http://blog.mediumequalsmessage.com/promise-deferred-objects-in-javascript-pt2-practical-use Intr ...

  2. Javascript - Promise学习笔记

    最近工作轻松了点,想起了以前总是看到的一个单词promise,于是耐心下来学习了一下.   一:Promise是什么?为什么会有这个东西? 首先说明,Promise是为了解决javascript异步编 ...

  3. 路由的Resolve机制(需要了解promise)

    angular的resovle机制,实际上是应用了promise,在进入特定的路由之前给我们一个做预处理的机会 1.在进入这个路由之前先懒加载对应的 .js $stateProvider .state ...

  4. angular2系列教程(七)Injectable、Promise、Interface、使用服务

    今天我们要讲的ng2的service这个概念,和ng1一样,service通常用于发送http请求,但其实你可以在里面封装任何你想封装的方法,有时候控制器之间的通讯也是依靠service来完成的,让我 ...

  5. 闲话Promise机制

    Promise的诞生与Javascript中异步编程息息相关,js中异步编程主要指的是setTimout/setInterval.DOM事件机制.ajax,通过传入回调函数实现控制反转.异步编程为js ...

  6. 深入理解jQuery、Angular、node中的Promise

    最初遇到Promise是在jQuery中,在jQuery1.5版本中引入了Deferred Object,这个异步队列模块用于实现异步任务和回调函数的解耦.为ajax模块.队列模块.ready事件提供 ...

  7. Promise的前世今生和妙用技巧

    浏览器事件模型和回调机制 JavaScript作为单线程运行于浏览器之中,这是每本JavaScript教科书中都会被提到的.同时出于对UI线程操作的安全性考虑,JavaScript和UI线程也处于同一 ...

  8. JavaScript进阶之路——认识和使用Promise,重构你的Js代码

    一转眼,这2015年上半年就过去了,差不多一个月没有写博客了,"罪过罪过"啊~~.进入了七月份,也就意味着我们上半年苦逼的单身生活结束了,从此刻起,我们要打起十二分的精神,开始下半 ...

  9. SQL Server-聚焦移除Bookmark Lookup、RID Lookup、Key Lookup提高SQL查询性能(六)

    前言 前面几节都是讲的基础内容,本节我们讲讲索引性能优化,当对大数据进行处理时首先想到的就是索引,一旦遇到这样的问题则手忙脚乱,各种查资料,为何平常不扎实基本功呢,我们由浅入深,简短的内容,深入的理解 ...

随机推荐

  1. ORACLE计算表引占用空间大小

    在ORACLE数据库中,如何计算一个表所占用的存储空间呢?我们可以通过系统视图DBA_SEGMENTS.USER_SEGMETNS.DBA_TABLES来查看一个表所占空间的大小,如下所示:   SE ...

  2. SQL Server:触发器详解

    1. 概述 触发器是一种特殊的存储过程,它不能被显式地调用,而是在往表中插入记录﹑更新记录或者删除记录时被自动地激活. 所以触发器可以用来实现对表实施复杂的完整性约束. 2. 触发器的分类 SQL S ...

  3. .NET应用架构设计—适当使用活动记录模式代替领域模型模式

    阅读目录: 1.背景介绍 2.简单介绍领域模型模式.活动记录模式 3.活动记录模式的简单示例及要点 4.总结 1.背景介绍 对软件开发方法论有兴趣的博友应该发现最近“领域驱动设计”慢慢的被人发现被人实 ...

  4. 解决undefined reference to `__poll_chk@GLIBC_2.16' 错误

    出现这个错误,是系统的glibc版本太低了,需要更新 到http://ftp.gnu.org/gnu/glibc/下载新版本的glibc,也不用太高,我选择glibc-2.20.tar.gz   解压 ...

  5. Live Migrate 操作 - 每天5分钟玩转 OpenStack(42)

    Migrate 操作会先将 instance 停掉,也就是所谓的“冷迁移”.而 Live Migrate 是“热迁移”,也叫“在线迁移”,instance不会停机. Live Migrate 分两种: ...

  6. 15、sql语句集,Linux 下PHP查询mysql

    一.mysql 创建数据库 mysql密码,默认没有 如果想更改的话, mysqladmin  -uroot   password  root123 登录   : [root@localhost ro ...

  7. Java关键字 ClassName.this(类名.this)的理解

    关键字this用于指代当前的对象.因此,类内部可以使用this作为前缀引用实例成员: this()代表了调用另一个构造函数,至于调用哪个构造函数根据参数表确定.this()调用只 能出现在构造函数的第 ...

  8. 更为简单的Ctrl+S自动刷新浏览器工具-LinrF5

    一款自动刷新浏览器的小工具,它通过监听用户的按键,如果在键盘按下 Ctrl+S ,则自动刷新浏览器,操作十分简单,前端开发必备神器,快速提升工作效率,支持IE.火狐以及最新版的chrome33.之前我 ...

  9. SSH----小小项目的小小总结

    嘛,之前学了一下SSH框架,跟人合作写了个小东西参加比赛,(当然我是队长),真的感慨良多~,现在用这篇博客记录下来吧 1.责任心/责任感 首先要说的一点,要有责任心,当你与别人组成一个团队的时候,虽然 ...

  10. UVALive - 3942 Remember the Word[Trie DP]

    UVALive - 3942 Remember the Word Neal is very curious about combinatorial problems, and now here com ...