JavaScript之promise_动力节点Java学院整理

在JavaScript的世界中,所有代码都是单线程执行的。

由于这个“缺陷”,导致JavaScript的所有网络操作,浏览器事件,都必须是异步执行。异步执行可以用回调函数实现:

function callback() {
 console.log('Done');
}
console.log('before setTimeout()');
setTimeout(callback, 1000); // 1秒钟后调用callback函数
console.log('after setTimeout()');

观察上述代码执行,在Chrome的控制台输出可以看到:

before setTimeout()
after setTimeout()
(等待1秒后)
Done

可见,异步操作会在将来的某个时间点触发一个函数调用。
AJAX就是典型的异步操作。以上一节的代码为例:

request.onreadystatechange = function () {
 if (request.readyState === 4) {
  if (request.status === 200) {
   return success(request.responseText);
  } else {
   return fail(request.status);
  }
 }
}

把回调函数success(request.responseText)和fail(request.status)写到一个AJAX操作里很正常,但是不好看,而且不利于代码复用。

有没有更好的写法?比如写成这样:

var ajax = ajaxGet('http://...');
ajax.ifSuccess(success)
 .ifFail(fail);

这种链式写法的好处在于,先统一执行AJAX逻辑,不关心如何处理结果,然后,根据结果是成功还是失败,在将来的某个时候调用success函数或fail函数。

古人云:“君子一诺千金”,这种“承诺将来会执行”的对象在JavaScript中称为Promise对象。

Promise有各种开源实现,在ES6中被统一规范,由浏览器直接支持。先测试一下你的浏览器是否支持Promise:

'use strict';

new Promise(function () {});
// 直接运行测试:
alert('支持Promise!');

我们先看一个最简单的Promise例子:生成一个0-2之间的随机数,如果小于1,则等待一段时间后返回成功,否则返回失败:

function test(resolve, reject) {
 var timeOut = Math.random() * 2;
 log('set timeout to: ' + timeOut + ' seconds.');
 setTimeout(function () {
  if (timeOut < 1) {
   log('call resolve()...');
   resolve('200 OK');
  }
  else {
   log('call reject()...');
   reject('timeout in ' + timeOut + ' seconds.');
  }
 }, timeOut * 1000);
}

这个test()函数有两个参数,这两个参数都是函数,如果执行成功,我们将调用resolve('200 OK'),如果执行失败,我们将调用reject('timeout in ' + timeOut + ' seconds.')。可以看出,test()函数只关心自身的逻辑,并不关心具体的resolve和reject将如何处理结果。
有了执行函数,我们就可以用一个Promise对象来执行它,并在将来某个时刻获得成功或失败的结果:

var p1 = new Promise(test);
var p2 = p1.then(function (result) {
 console.log('成功:' + result);
});
var p3 = p2.catch(function (reason) {
 console.log('失败:' + reason);
});

变量p1是一个Promise对象,它负责执行test函数。由于test函数在内部是异步执行的,当test函数执行成功时,我们告诉Promise对象:

// 如果成功,执行这个函数:
p1.then(function (result) {
 console.log('成功:' + result);
});

当test函数执行失败时,我们告诉Promise对象:

p2.catch(function (reason) {
 console.log('失败:' + reason);
});

Promise对象可以串联起来,所以上述代码可以简化为:

new Promise(test).then(function (result) {
 console.log('成功:' + result);
}).catch(function (reason) {
 console.log('失败:' + reason);
});

实际测试一下,看看Promise是如何异步执行的:

'use strict';

// 清除log:
var logging = document.getElementById('test-promise-log');
while (logging.children.length > 1) {
 logging.removeChild(logging.children[logging.children.length - 1]);
}

// 输出log到页面:
function log(s) {
 var p = document.createElement('p');
 p.innerHTML = s;
 logging.appendChild(p);
}
new Promise(function (resolve, reject) {
 log('start new Promise...');
 var timeOut = Math.random() * 2;
 log('set timeout to: ' + timeOut + ' seconds.');
 setTimeout(function () {
  if (timeOut < 1) {
   log('call resolve()...');
   resolve('200 OK');
  }
  else {
   log('call reject()...');
   reject('timeout in ' + timeOut + ' seconds.');
  }
 }, timeOut * 1000);
}).then(function (r) {
 log('Done: ' + r);
}).catch(function (reason) {
 log('Failed: ' + reason);
});

Log:

start new Promise...
set timeout to: 0.5354042750614991 seconds.
call resolve()...
Done: 200 OK

可见Promise最大的好处是在异步执行的流程中,把执行代码和处理结果的代码清晰地分离了:

Promise还可以做更多的事情,比如,有若干个异步任务,需要先做任务1,如果成功后再做任务2,任何任务失败则不再继续并执行错误处理函数。
要串行执行这样的异步任务,不用Promise需要写一层一层的嵌套代码。有了Promise,我们只需要简单地写:

job1.then(job2).then(job3).catch(handleError);

其中,job1、job2和job3都是Promise对象。
下面的例子演示了如何串行执行一系列需要异步计算获得结果的任务:

'use strict';

var logging = document.getElementById('test-promise2-log');
while (logging.children.length > 1) {
 logging.removeChild(logging.children[logging.children.length - 1]);
}

function log(s) {
 var p = document.createElement('p');
 p.innerHTML = s;
 logging.appendChild(p);
}
// 0.5秒后返回input*input的计算结果:
function multiply(input) {
 return new Promise(function (resolve, reject) {
  log('calculating ' + input + ' x ' + input + '...');
  setTimeout(resolve, 500, input * input);
 });
}

// 0.5秒后返回input+input的计算结果:
function add(input) {
 return new Promise(function (resolve, reject) {
  log('calculating ' + input + ' + ' + input + '...');
  setTimeout(resolve, 500, input + input);
 });
}

var p = new Promise(function (resolve, reject) {
 log('start new Promise...');
 resolve(123);
});

p.then(multiply)
 .then(add)
 .then(multiply)
 .then(add)
 .then(function (result) {
 log('Got value: ' + result);
});

Log:

start new Promise...
calculating 123 x 123...
calculating 15129 + 15129...
calculating 30258 x 30258...
calculating 915546564 + 915546564...
Got value: 1831093128

setTimeout可以看成一个模拟网络等异步执行的函数。现在,我们把上一节的AJAX异步执行函数转换为Promise对象,看看用Promise如何简化异步处理:

'use strict';

// ajax函数将返回Promise对象:
function ajax(method, url, data) {
 var request = new XMLHttpRequest();
 return new Promise(function (resolve, reject) {
  request.onreadystatechange = function () {
   if (request.readyState === 4) {
    if (request.status === 200) {
     resolve(request.responseText);
    } else {
     reject(request.status);
    }
   }
  };
  request.open(method, url);
  request.send(data);
 });
}
var log = document.getElementById('test-promise-ajax-result');
var p = ajax('GET', '/api/categories');
p.then(function (text) { // 如果AJAX成功,获得响应内容
 log.innerText = text;
}).catch(function (status) { // 如果AJAX失败,获得响应代码
 log.innerText = 'ERROR: ' + status;
});

除了串行执行若干异步任务外,Promise还可以并行执行异步任务。
试想一个页面聊天系统,我们需要从两个不同的URL分别获得用户的个人信息和好友列表,这两个任务是可以并行执行的,用Promise.all()实现如下:

var p1 = new Promise(function (resolve, reject) {
 setTimeout(resolve, 500, 'P1');
});
var p2 = new Promise(function (resolve, reject) {
 setTimeout(resolve, 600, 'P2');
});
// 同时执行p1和p2,并在它们都完成后执行then:
Promise.all([p1, p2]).then(function (results) {
 console.log(results); // 获得一个Array: ['P1', 'P2']
});

有些时候,多个异步任务是为了容错。比如,同时向两个URL读取用户的个人信息,只需要获得先返回的结果即可。这种情况下,用Promise.race()实现:

var p1 = new Promise(function (resolve, reject) {
 setTimeout(resolve, 500, 'P1');
});
var p2 = new Promise(function (resolve, reject) {
 setTimeout(resolve, 600, 'P2');
});
Promise.race([p1, p2]).then(function (result) {
 console.log(result); // 'P1'
});

由于p1执行较快,Promise的then()将获得结果'P1'。p2仍在继续执行,但执行结果将被丢弃。

如果我们组合使用Promise,就可以把很多异步任务以并行和串行的方式组合起来执行。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • JavaScript Promise 用法

    同步编程通常来说易于调试和维护,然而,异步编程通常能获得更好的性能和更大的灵活性.异步的最大特点是无需等待."Promises"渐渐成为JavaScript里最重要的一部分,大量的新API都开始promise原理实现.下面让我们看一下什么是promise,以及它的API和用法! Promises现状 XMLHttpRequest API是异步的,但它没有使用promise API.但有很多原生的 javascript API 使用了promise: *Battery API *fetc

  • 理解JavaScript中Promise的使用

    Javascript 采用回调函数(callback)来处理异步编程.从同步编程到异步回调编程有一个适应的过程,但是如果出现多层回调嵌套,也就是我们常说的厄运的回调金字塔(Pyramid of Doom),绝对是一种糟糕的编程体验.于是便有了 CommonJS 的 Promises/A 规范,用于解决回调金字塔问题.本文先介绍 Promises 相关规范,然后再通过解读一个迷你的 Promises 以加深理解. 什么是 Promise 一个 Promise 对象代表一个目前还不可用,但是在未来的

  • NodeJS中利用Promise来封装异步函数

    在写Node.js的过程中,连续的IO操作可能会导致"金字塔噩梦",回调函数的多重嵌套让代码变的难以维护,利用CommonJs的Promise来封装异步函数,使用统一的链式API来摆脱多重回调的噩梦. Node.js提供的非阻塞IO模型允许我们利用回调函数的方式处理IO操作,但是当需要连续的IO操作时,你的回调函数会多重嵌套,代码很不美观,而且不易维护,而且可能会有许多错误处理的重复代码,也就是所谓的"Pyramid of Doom". 复制代码 代码如下: ste

  • JavaScript异步回调的Promise模式封装实例

    网页的交互越来越复杂,JavaScript 的异步操作也随之越来越多.如常见的 ajax 请求,需要在请求完成时响应操作,请求通常是异步的,请求的过程中用户还能进行其他的操作,不会对页面进行阻塞,这种异步的交互效果对用户来说是挺有友好的.但是对于开发者来说,要大量处理这种操作,就很不友好了.异步请求完成的操作必须预先定义在回调函数中,等到请求完成就必须调用这个函数.这种非线性的异步编程方式会让开发者很不适应,同时也带来了诸多的不便,增加了代码的耦合度和复杂性,代码的组织上也会很不优雅,大大降低了

  • JavaScript异步编程Promise模式的6个特性

    在我们开始正式介绍之前,我们想看看Javascript Promise的样子: 复制代码 代码如下: var p = new Promise(function(resolve, reject) {  resolve("hello world");}); p.then(function(str) {  alert(str);}); 1. then()返回一个Forked Promise 以下两段代码有什么区别呢? 复制代码 代码如下: // Exhibit Avar p = new Pr

  • node.js中使用q.js实现api的promise化

    关于啥是promise以及promise解决的是啥问题,敬请体验node的回调异步编码大法,顺带移步http://wiki.commonjs.org/wiki/Promises/A 看看是咋定义的,在此不再赘述. 这里我们看看怎么用q.js 实现node api的promise. 一.万事开始皆为install 复制代码 代码如下: npm install q 二.标准node style api 的promise化方法 1.使用Q.nfcall 相对于Q.fcall ,Q.nfcall 就是n

  • JavaScript中的Promise使用详解

    许多的语言,为了将异步模式处理得更像平常的顺序,都包含一种有趣的方案库,它们被称之为promises,deferreds,或者futures.JavaScript的promises ,可以促进关注点分离,以代替紧密耦合的接口. 本文讲的是基于Promises/A 标准的JavaScript promises.[http://wiki.commonjs.org/wiki/Promises/A] Promise的用例: 执行规则 多个远程验证 超时处理 远程数据请求 动画 将事件逻辑从应用逻辑中解耦

  • 详细解读JavaScript编程中的Promise使用

    Promise核心说明 尽管Promise已经有自己的规范,但目前的各类Promise库,在Promise的实现细节上是有差异的,部分API甚至在意义上完全不同.但Promise的核心内容,是相通的,它就是then方法.在相关术语中,promise指的就是一个有then方法,且该方法能触发特定行为的对象或函数. Promise可以有不同的实现方式,因此Promise核心说明并不会讨论任何具体的实现代码. 先阅读Promise核心说明的意思是:看,这就是需要写出来的结果,请参照这个结果想一想怎么用

  • 举例详解JavaScript中Promise的使用

    摘录 – Parse JavaScript SDK现在提供了支持大多数异步方法的兼容jquery的Promises模式,那么这意味着什么呢,读完下文你就了解了. "Promises" 代表着在javascript程序里下一个伟大的范式,但是理解他们为什么如此伟大不是件简单的事.它的核心就是一个promise代表一个任务结果,这个任务有可能完成有可能没完成.Promise模式唯一需要的一个接口是调用then方法,它可以用来注册当promise完成或者失败时调用的回调函数,这在Common

  • Javascript中的异步编程规范Promises/A详细介绍

    Javascript里异步编程逐渐被大家接受,先前大家一般通过回调嵌套,setTimeout.setInterval等方式实现,代码看起来非常不直观,不看整个代码逻辑很难快速理解.Javascript里异步函数大概有I/O函数(Ajax.postMessage.img load.script load等).计时函数(setTimeout.setInterval)等. 这些我们都很熟悉,在复杂的应用中往往会嵌套多层,甚至以为某些步骤未完成而导致程序异常,最简单的例子:比如你往DOM中注入节点,你必

随机推荐