怎么用promise js 实现实现异步控制

每个异步方法都返回一个Promise
每一个Promise &都有一个叫then 的方法,
接受一对callback & &被解决时调用,resolve,
被拒绝 & rejected 遇到错误
obj.save().then(function(obj){
}, function(error) {
第二个参数可选
try, catch, 和final方法
obj.save().try(function(obj) {
}).catch(function(error) {
}).finally(function(){
兼容其他Promise库,
AV.Promise#done & try
AV.Promise#fail & &catch
AV.Promise#always finally
obj.save().done(function(obj){
}).fail(function(error) {
}).always(function(){
将Promise 组织在一起
Promise 比较神奇, 代替多层嵌套方式来解决异步请求代码的调用顺序问题。
如果一个Promise的回调会返回一个Promise 那么
第二个then 里的callback 在第一个then 的callback 没有解决前是不会解决的.
Promise Chain
var query = new AV.Query('Student');
query.descending("gpa");
query.find().then(function(students){
  students[0].set("valedictorian",true);
  return students[0].save();
}).then(function(valedictorian) {
  return query.find();
}).then(function(students){
  students[1].set("salutatorian",true);
  return students[1].save();
}).then(function(salutatorian){
一个链中的Promise 返回一个错误的话, 所有成功的callback
在接下来都会被跳过直到遇到一个处理错误的callback
转换error 或者返回一个新的promise 来处理它.
var query = new AV.Query('Student');
query.descending('gpa');
query.find().then(function(students) {
  students[0].set("valedictorian",true);
  return AV.Promise.error("THere was an error.");
}).then(function(valedicator) {
  return query.find();
}).then(function(students) {
  students[1].set('salutatorian',true);
  return students[1].save();
}, function(error) {
  return AV.Promise.as('Hello');
}).then(funciton(hello) {
在正常情况的回调函数的未尾,加一个错误处理
的回调函数,是一种很常见的做法&
var query = new AV.Query("Student");
query.descending("gpa");
query.find().try(function(students) {
  students[0].set("valedictorian", true);
  return AV.Promise.error("There was an error.";)
}).try(function(valedictorian) {
  return query.find();
}).try(function() {
  students[1].set()
}).catch(function(error) {
  return AV.Promise.as("Hello!");
}).catch(fucntion(error) {
find save 会生成Promise
创建Promise
resolve &reject 来触发它的callback
var successful = new AV.Promise();
successful.resolve("the good result");
var failed = new AV.Promise();
failed.reject("An error message.");
var successful = AV.Promise().as("The good reulst.";)
var failed = AV.Promise.error("The error message".
var promise = new AV.Promise(function(resolve,reject) {
  resolve(42);
promise.then(function(ret) {
  console.log(ret);
顺序的Promise
某一行的数据做一系列的任务的时候, Promise 链很方便的,
每一个任务都等着前一个任务结束。
删除你的博客上的所有评论。
var query = new AV.Query("Comment");
query.equal("post",post);
query.find().then( function(results) {
  var promise = AV.Promise.as();
  _.each(results, function(result) {
    promise = promise.then(function() {
      return result.destroy();
    })
}).then(function() {
并行的Promise
多个任务, & when &方法, 开始几个操作。&
作用AV.Promise.when 来创建一个新的promise
Promise 被resolve 之后ft才被resolve
更多消耗系统资源和带宽。
function timePromisefy(delay) {
  return new AV.Promise(function(resolve) {
    setTimeout(function() {
      resolve(delay);
    })
var startDate = Date.now();
AV.Promise.when(
  timePromisefy(1),
  timePromisefy(32),
  timePromisefy(64),
  timePromisefy(128)
)).then(function(r1,r2,r3,r4) {
  console.log(new Date() - startDate);
var startDate = Date.now();
AV.Promise.when(
var query = new AV.Query("Comment");
query.equalTo("post",post);
query.find().then(function(results) {
  var promise = [];
  _.each( results, &function(result) {
    promises.push(result.destroy());
  return AV.Promise.when(promises);
}).then(function() {
错误处理中返回所有遇到的错误信息,以数组形式提供。
AV.Promise.all
数组形式的输入, 错误处理, 而不是等所有promise 完成.
AV.Promise.all([
  timerPromisefy(1),
  timerPromisefy(32)
]).then( function(values) {
AV.Promise.race 方法按收一个 promise 数组接入,扔resolve 时或者reject&
该函数会函回 &resolve &或者reject&
var p1 = new AV.Promise.as(1),
  p2 = new AV.Promise.as(2),
  p3 = new AV.Promise.as(3);
Promise.race([p1,p2,p3]).then(function(value) {
  console.log(value);
var delay= function(millis) {
  var promise = new AV.Promise();
  setTimeout()function() {
    pormise.resolove();
  },millis);
delay(100).then(function(){
AV.Promise.setPromisesAPLusCompiant(true);
process.on('uncaughtException');
AV.Promise.setDebugError(true);
阅读(...) 评论()异步JavaScript编程中的Promise使用方法
作者:梁砫
字体:[ ] 类型:转载 时间:
这篇文章主要介绍了异步JavaScript编程中的Promise使用方法,包含Ajax的结合操作等问题,需要的朋友可以参考下
我在很多地方都看到过异步(Asynchronous)这个词,但在我还不是很理解这个概念的时候,却发现自己常常会被当做“已经很清楚”(* ̄? ̄)。
如果你也有类似的情况,没关系,搜索一下这个词,就可以得到大致的说明。在这里,我会对JavaScript的异步做一点额外解释。
看一下这段代码:
var start = new Date();
setTimeout(function(){
var end = new Date();
console.log("Time elapsed: ", end - start, "ms");
while (new Date - start & 1000) {};
这段代码运行后会得到类似Time elapsed: 1013ms这样的结果。 setTimeout()所设定的在未来500ms时执行的函数,实际等了比1000ms更多的时间后才执行。
要如何解释呢?调用setTimeout()时,一个延时事件被排入队列。然后,继续执行这之后的代码,以及更后边的代码,直到没有任何代码。没有任何代码后,JavaScript线程进入空闲,此时JavaScript执行引擎才去翻看队列,在队列中找到“应该触发”的事件,然后调用这个事件的处理器(函数)。处理器执行完成后,又再返回到队列,然后查看下一个事件。
单线程的JavaScript,就是这样通过队列,以事件循环的形式工作的。所以,前面的代码中,是用while将执行引擎拖在代码运行期间长达1000ms,而在全部代码运行完回到队列前,任何事件都不会触发。这就是JavaScript的异步机制。
JavaScript的异步难题
JavaScript中的异步操作可能不总是简单易行的。
Ajax也许是我们用得最多的异步操作。以jQuery为例,发起一个Ajax请求的代码一般是这样的:
// Ajax请求示意代码
data: dataObject,
success: function(){},
error: function(){}
这样的写法有什么问题吗?简单来说,不够轻便。为什么一定要在发起请求的地方,就要把success和error这些回调给写好呢?假如我的回调要做很多很多的事情,是要我想起一件事情就跑回这里添加代码吗?
再比如,我们要完成这样一件事:有4个供Ajax访问的url地址,需要先Ajax访问第1个,在第1个访问完成后,用拿到的返回数据作为参数再访问第2个,第2个访问完成后再第3个...以此到4个全部访问完成。按照这样的写法,似乎会变成这样:
url: url1,
success: function(data){
url: url2,
data: data,
success: function(data){
你一定会觉得这种称为Pyramid of Doom(金字塔厄运)的代码看起来很糟糕。习惯了直接附加回调的写法,就可能会对这种一个传递到下一个的异步事件感到无从入手。为这些回调函数分别命名并分离存放可以在形式上减少嵌套,使代码清晰,但仍然不能解决问题。
另一个常见的难点是,同时发送两个Ajax请求,然后要在两个请求都成功返回后再做一件接下来的事,想一想如果只按前面的方式在各自的调用位置去附加回调,这是不是好像也有点难办?
适于应对这些异步操作,可以让你写出更优雅代码的就是Promise。
Promise上场
Promise是什么呢?先继续以前面jQuery的Ajax请求示意代码为例,那段代码其实可以写成这个样子:
var promise = $.ajax({
data: dataObject
promise.done(function(){});
promise.fail(function(){});
这和前面的Ajax请求示意代码是等效的。可以看到,Promise的加入使得代码形式发生了变化。Ajax请求就好像变量赋值一样,被“保存”了起来。这就是封装,封装将真正意义上让异步事件变得容易起来。
封装是有用的
Promise对象就像是一个封装好的对异步事件的引用。想要在这个异步事件完成后做点事情?给它附加回调就可以了,不管附加多少个也没问题!
jQuery的Ajax方法会返回一个Promise对象(这是jQuery1.5重点增加的特性)。如果我有do1()、do2()两个函数要在异步事件成功完成后执行,只需要这样做:
promise.done(do1);
// Other code here.
promise.done(do2);
这样可要自由多了,我只要保存这个Promise对象,就在写代码的任何时候,给它附加任意数量的回调,而不用管这个异步事件是在哪里发起的。这就是Promise的优势。
正式的介绍
Promise应对异步操作是如此有用,以至于发展为了CommonJS的一个规范,叫做Promises/A。Promise代表的是某一操作结束后的返回值,它有3种状态:
&&& 肯定(fulfilled或resolved),表明该Promise的操作成功了。
&&& 否定(rejected或failed),表明该Promise的操作失败了。
&&& 等待(pending),还没有得到肯定或者否定的结果,进行中。
此外,还有1种名义上的状态用来表示Promise的操作已经成功或失败,也就是肯定和否定状态的集合,叫做结束(settled)。Promise还具有以下重要的特性:
&&& 一个Promise只能从等待状态转变为肯定或否定状态一次,一旦转变为肯定或否定状态,就再也不会改变状态。
&&& 如果在一个Promise结束(成功或失败,同前面的说明)后,添加针对成功或失败的回调,则回调函数会立即执行。
想想Ajax操作,发起一个请求后,等待着,然后成功收到返回或出现错误(失败)。这是否和Promise相当一致?
进一步解释Promise的特性还有一个很好的例子:jQuery的$(document).ready(onReady)。其中onReady回调函数会在DOM就绪后执行,但有趣的是,如果在执行到这句代码之前,DOM就已经就绪了,那么onReady会立即执行,没有任何延迟(也就是说,是同步的)。
Promise示例
生成Promise
Promises/A里列出了一系列实现了Promise的JavaScript库,jQuery也在其中。下面是用jQuery生成Promise的代码:
var deferred = $.Deferred();
deferred.done(function(message){console.log("Done: " + message)});
deferred.resolve("morin"); // Done: morin
jQuery自己特意定义了名为Deferred的类,它实际上就是Promise。$.Deferred()方法会返回一个新生成的Promise实例。一方面,使用deferred.done()、deferred.fail()等为它附加回调,另一方面,调用deferred.resolve()或deferred.reject()来肯定或否定这个Promise,且可以向回调传递任意数据。
合并Promise
还记得我前文说的同时发送2个Ajax请求的难题吗?继续以jQuery为例,Promise将可以这样解决它:
var promise1 = $.ajax(url1),
promise2 = $.ajax(url2),
promiseCombined = $.when(promise1, promise2);
promiseCombined.done(onDone);
$.when()方法可以合并多个Promise得到一个新的Promise,相当于在原多个Promise之间建立了AND(逻辑与)的关系,如果所有组成Promise都已成功,则令合并后的Promise也成功,如果有任意一个组成Promise失败,则立即令合并后的Promise失败。
级联Promise
再继续我前文的依次执行一系列异步任务的问题。它将用到Promise最为重要的.then()方法(在Promises/A规范中,也是用“有then()方法的对象”来定义Promise的)。代码如下:
var promise = $.ajax(url1);
promise = promise.then(function(data){
return $.ajax(url2, data);
promise = promise.then(function(data){
return $.ajax(url3, data);
Promise的.then()方法的完整形式是.then(onDone, onFail, onProgress),这样看上去,它像是一个一次性就可以把各种回调都附加上去的简便方法(.done()、.fail()可以不用了)。没错,你的确可以这样使用,这是等效的。
但.then()方法还有它更为有用的功能。如同then这个单词本身的意义那样,它用来清晰地指明异步事件的前后关系:“先这个,然后(then)再那个”。这称为Promise的级联。
要级联Promise,需要注意的是,在传递给then()的回调函数中,一定要返回你想要的代表下一步任务的Promise(如上面代码的$.ajax(url2, data))。这样,前面被赋值的那个变量才会变成新的Promise。而如果then()的回调函数返回的不是Promise,则then()方法会返回最初的那个Promise。
应该会觉得有些难理解?从代码执行的角度上说,上面这段带有多个then()的代码其实还是被JavaScript引擎运行一遍就结束。但它就像是写好的舞台剧的剧本一样,读过一遍后,JavaScript引擎就会在未来的时刻,依次安排演员按照剧本来演出,而演出都是异步的。then()方法就是让你能写出异步剧本的笔。
将Promise用在基于回调函数的API
前文反复用到的$.ajax()方法会返回一个Promise对象,这其实只是jQuery特意提供的福利。实际情况是,大多数JavaScript API,包括Node.js中的原生函数,都基于回调函数,而不是基于Promise。这种情况下使用Promise会需要自行做一些加工。
这个加工其实比较简单和直接,下面是例子:
var deferred = $.Deferred();
setTimeout(deferred.resolve, 1000);
deferred.done(onDone);
这样,将Promise的肯定或否定的触发器,作为API的回调传入,就变成了Promise的处理模式了。
Promise是怎么实现出来的?
本文写Promise写到这里,你发现了全都是基于已有的实现了Promise的库。那么,如果要自行构筑一个Promise的话呢?
位列于Promises/A的库列表第一位的Q可以算是最符合Promises/A规范且相当直观的实现。如果你想了解如何做出一个Promise,可以参考Q提供的设计模式解析。
限于篇幅,本文只介绍Promise的应用。我会在以后单独开一篇文章来详述Promise的实现细节。
作为JavaScript后续版本的ECMAScript 6将原生提供Promise,如果你想知道它的用法,推荐阅读JavaScript Promises: There and back again。
Promise这个词顽强到不适合翻译,一眼之下都会觉得意义不明。不过,在JavaScript里做比较复杂的异步任务时,它的确可以提供相当多的帮助。
您可能感兴趣的文章:
大家感兴趣的内容
12345678910
最近更新的内容
常用在线小工具接触过promise的的都知道它的应用场景和用途,Promise可以用来避免异步操作函数里的嵌套回调(callback hell)问题,因为解决异步最直接的方法是回调嵌套,将后一个的操作放在前一个操作的异步回调里,但如果操作多了,就会有很多层的嵌套。
Promise的实现方式比较多,有丰富的第三方库,ES6也已经原生支持了Promise,jquery中也有$.Deferred()等可以解决异步嵌套问题。
先给下Promise学术点的:
promise代表一个异步操作的执行返回状态,这个执行返回状态在promise对象创建时未必已知。它允许你为异步操作的成功或失败指定处理方法。 这使得异步方法可以像同步方法那样返回值:异步方法会返回一个包含了原返回状态的 promise 对象来替代原返回状态。
一、Promise的适用场景
Promise并非适用于所有的异步场景,例如事件的绑定,某个程度上Promise有点类似事件的监听回调,当触发某个操作时进行后面特定的逻辑。但Promise只能执行一次,且需要前面特定的操作执行完成才会进行下一步,一般分成功和失败两种场景,成功或失败后会立即执行响应函数。这就很适合判断一个比较耗时的操作是否最终执行成功的场景,就如我们通常理解的ajax网络请求、读取localstorage等操作。
二、Promise的表现
如果使用回调方法处理多个操作的异步场景,判断某个操作成功或失败的控制在于声明的匿名函数里面,使用Promise对象则可以重新定义异步执行的状态和控制逻辑。
promises的最重要的特点就是它把我们处理任何函数调用的成功或者失败的方式规范成了可预测的形式,特别是如果这个调用实际上的异步的。
Promise中有几个状态:
pending: 初始状态。 非 fulfilled 或 rejected。
resolved: 成功的操作。也有的成为fulfilled 。
rejected: 失败的操作。
不同的Promise差异基本表现如下:
构造Promise对象 new Promise().resolve() 或者 new Pomise(function(resolve, reject) {})
是否有 .done() .fail() .always() 等方法
是否有Promise.all()方法
是否有isRejected() isResolved()
.then() return 结果链式的
三、几种规范的promise
2.1、Promise的Promise/A 规范和Promise/A+规范
先看下规范的地址: monjs.org/wiki/Promises/A /
什么是A+规范的Promise? Promises/A+是在Promises/A的基础上对原有规范进行修正和增强。
Promise A+与Promise A的主要区别:
符合Promise/A+规范的promise实现均以then方法为交互核心。Promises/A+组织会因新发现的问题以向后兼容的方式修改规范,因此Promises/A+规范相对来说还是比较稳定的。
A+规范强调了不同实现之间的互操作和混用,通过术语thenable来区分promise对象,当一个对象拥有then函数就认为是promise对象
A+定义当onFulfilled或者onRejected返回promise时后的处理过程,他们必须是作为函数来调用,而且调用过程必须是异步的
A+严格定义了then方法链式调用时onFulfilled或者onRejected的调用顺序
目前判断是否为Promise/ A+规范主要看Promiise的方法含有new Pomise(function(resolve, reject) {})、then、resolve、all等方法。ES6 Promise的实现严格遵循了Promise/A+规范。例如Defferd就不是Promise/ A+的规范。
2.2、Defferd实现规范
比较典型的是jquery的Defferd方法实现的Promise,另外jquery还有一个Promise的类型,实现的原理相同,但是不遵循Promise/A +规范,相对于Promise没有那么稳定。
我们先来看看jquery的Promise是怎样实现的。我们看下jquery的Deferred实现源码:
// 精简后主要逻辑的源码
Deferred: function( func ) {
var tuples = [
// action, add listener, listener list, final state
[ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
[ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
[ "notify", "progress", jQuery.Callbacks("memory") ]
state = "pending",
promise = {
state: function() {},
always: function() {},
then: function( /* fnDone, fnFail, fnProgress */ ) { },
// Get a promise for this deferred
// If obj is provided, the promise aspect is added to the object
promise: function( obj ) {}
deferred = {};
jQuery.each( tuples, function( i, tuple ) {
deferred[ tuple[0] + "With" ] = list.fireW
promise.promise( deferred );
// All done!
// 调用时间订阅方法
var def = $.Deferred();
def.done(function(){
console.log(“成功”);
}).fail(function(){
console.log(“失败”);
}).catch(function(){
console.log(“再一次成功”);
可见,jquery的Deferred是个工厂类,返回的是内部构建的deferred对象;tuples 含有三个$.Callbacks对象,分别表示成功,失败,处理中三种状态;创建的promise对象,具有state、always、then、primise方法;扩展primise对象生成最终的Deferred对象,返回该对象;没有resolve、reject、all等Promise/A+ 规范的常用方法。
三、兼容性
目前使用需要使用polyfill,也就是原生实现一个Promise支持较低浏览器,第三方实现库很多后面给了个学习的较好例子。
四、generator的异步
Promise处理异步问题相信都了解了。ES6里的generator还有另一个处理异步的方法,那ES6定义这两个特性岂不是重复了?
单独地介绍Generator没有太大价值,因为它除了更复杂外,功能与普通函数没有太大差别。真正让Generator具有价值的是yield关键字,这个yield关键字让Generator内部的逻辑能够切割成多个部分。并且可以灵活控制内部的执行情况。
// 申明要用 var gen = function* (){} 的方式
var compute = function* (a, b) {
yield console.log(a + b);
yield console.log(a - b);
yield console.log(a * b);
yield console.log(a / b);
var generator = compute(4, 2);
generator.next(); // 6
generator.next(); // 2
generator.next(); // 8
generator.next(); // 2
运行时使用node –harmony-generators test.js
不难发现它的运行过程,generator函数运行到yield时会停止,等待下一个next()方法调用让它继续执行。我们改下成为异步方法,异步我们需要借助高阶函数
var helper = function(fn) {
return function() {
var args = [].slice.call(arguments);
args.push(function() { // 在回调函数中植入收集逻辑
if (pass) {
pass.apply(null, arguments);
fn.apply(null, args);
return function(fn) { // 传入一个收集函数
那么后面的写法做下修改
var sum = helper(function sum(a, b){
console.log(a + b);
var minus = helper(function minus(a, b){
console.log(a - b);
var muti= helper(function muti(a, b){
console.log(a * b);
var devide= helper(function devide(a, b){
console.log(a / b);
var compute = function*(a, b) {
yield sum(a, b);
yield minus(a, b);
yield muti(a, b);
yield devide(a, b);
var generator = compute(5, 2);
var state = generator.next();
* next 返回 {value:'',done: false}的结构,value表示执行传入的值,done表示是否结束
{[type]} !state.done [description]
* @return {[type]}
[description]
setTimeout(function() {
while (!state.done) {
state = generator.next();
console.log('other');
generator实现异步的方法也有比较完整的封装方式,实现先可以看:/ouvens/co 可以看个简单版的:
var co = function(flow) {
var generator = flow();
var next = function(data) {
var result = generator.next(data);
if (!result.done) {
result.value(function(err, data) {
if (err) {
next(data);
我们小结一下通过Generator进行流程控制的特点。 – 每个异步方法都需要标准化为yield关键字能接受的方法,使我们有机会注入特殊逻辑,这个过程被称为thunkify。 – 需要巧妙地将异步调用执行完成得到的结果通过.next()传递给下一段流程。 – 需要递归地将业务逻辑执行完成。
需要注意的是yield只能暂停Generator内部的逻辑,它并不是真正暂停整个线程,Generator外的业务逻辑依然会继续执行下去。
实现异步的方法目前有,自定义嵌套,Promise、generator、Defferd,还有ES7的async(其实是generator的封装),不同场景可以选择不同的方式去实现
简单的Promise实现样例: /ouvens/promise
generator异步实现: /tj/co
参考文章: q.com/cn/articles/generator-and-asynchronous-programming/ https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise /promise-aplus-implementation/ /zh/tutorials/es6/promises/ https://blog.domenic.me/youre-missing-the-point-of-promises/#toc_1 /nodejs/node-v0.x-archive/wiki/modules#async-flow http://www./article/JavaScript-tips-on-how-to-implement-a-ECMAScript-6-promise-patch
分享到: ()
专注品牌化高端网站建设基于 generator 与 promise 的异步编程解决方案 - CNode技术社区
积分: 3350
人因梦想而伟大。
原文发表于
转载请注明出处
一直以来,“异步”编程问题一直困扰着广大的 JavaScript 开发者。近年来出现了各种异步解决方案,从基于最原始的callback方式的async函数,到promise标准,再到基于generator的co库,以及即将纳入 ES7 标准的async function / await语法,但是由于各种现实的原因,它们的表现并不尽人意。
原始的callback方式简单明了,不需要过多的依赖,但是在异步逻辑较复杂的场景下写出来的程序并不太直观,就我个人的使用经验而言,尽管多年来已经练就了一身可以穿梭在各种嵌套回调的“乱码”之中,每次重新看这些代码都头疼不已。
JavaScript 异步解决方案都是朝着更直观(跟写同步代码一样)的方向发展的,比如近来呼声最高的async function / await语法,直接从语言层面解决问题,使用体验那是好得没法说的。但是,这是一个 ES7 (ES2017,即明年才会发布的 ES 标准)标准的语法,目前并没有得到各 JavaScript 引擎的内置支持。虽然我们照样可以使用 Babel 神器来将它编译成 ES5 / ES6 的语法,然后运行在现有的 JavaScript 引擎之上。然而使用 Babel 编译后的代码并不易于维护,首先这些代码修改后要先经过一次编译,当我们在生产环境上执行编译后的代码时,很难准确地定位到源码出错的位置。另外,根据最新可靠的消息,Node
v7 版本会在语法层面上支持async function / await语法,但该版本原计划于 9 月 30 号发布却跳票了,而且按照往年的惯例,也要在 1 年后发布的 Node v8 LTS 版本上才会正式支持该语法,这对于追求稳定的企业来说还需要一个漫长的等待过程。
通过 Babel 编译 async function / await
语法解决方案
利用async function / await语法,我们可以很直观地书写异步程序:
// sleep函数,返回一个Promise对象
function sleep(ms) {
return new Promise((resolve, reject) =& {
setTimeout(resolve, ms);
async function test() {
// 循环100次
for (let i = 0; i & 100; i++) {
// 等待100ms再返回
await sleep(100);
但由于目前的 JavaScript 引擎均不支持该语法,需要通过 Babel 之类的工具编译成 ES6 语法后的程序是这样的:
&use strict&;
function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info. } catch (error) { reject(error); } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { return step(&next&, value); }, function (err) { return step(&throw&, err); }); } } return step(&next&); }); }; }
function sleep(ms) {
return new Promise((resolve, reject) =& {
setTimeout(resolve, ms);
let test = function () {
var ref = _asyncToGenerator(function* () {
for (let i = 0; i & 100; i++) {
yield sleep(100);
return function test() {
return ref.apply(this, arguments);
从编译后的代码来看,虽然在前面增加了一坨_asyncToGenerator函数的代码,但test函数的代码还是能看出程序原来的结构。通过以上凌乱的代码我们还是可以看出,其内部是通过generator function实现的,在外层返回一个promise对象。
基于 generator 与 promise 的解决方案
首先我们要达成这样的共识:async function / await语法是未来的主流,但是要让主流的 JavaScript 引擎支持该语法还需要一个很漫长的过程,而现在我们需要找到一种替代的方法,而这种方法又能尽量保持与async function / await非常相近,在以后可以很轻易地替换成新的用法。 基于以上的考虑可以得出以下结论:
异步函数执行后需要返回一个promise对象(async function执行后返回的也是一个promise对象)
使用generator function,yield代替await,这样可以最大程度上保持程序逻辑结构不变(generator function从 Node v4 已经开始支持,经过两年多的使用验证,性能和可靠性性上还是有保证的)
基于以上两点的考虑,我们可以假设新的异步代码应该是这样的:
// sleep函数,返回一个Promise对象
function sleep(ms) {
return new Promise((resolve, reject) =& {
setTimeout(resolve, ms);
// 通过coroutine来包装异步函数
const test = coroutine(function* () {
// 循环100次
for (let i = 0; i & 100; i++) {
// 等待100ms再返回
yield sleep(100);
console.log('i=%s', i);
// 返回执行sleep次数
return 100;
// 执行函数,其返回一个Promise对象
.then(i =& console.log('执行了%s次sleep', i))
.catch(err =& console.error('出错', err));
对比直接使用async function / await语法,我们发现只是在声明异步函数和yield这两行写法不同,它可以在 Node v4 及更高版本上可直接执行,并且可以直接在源码上进行调试。以下是上文的程序在 Visual Studio Code 上进行调试的界面(coroutine函数的实现将在下文讲解):
实现一个简单的 coroutine 函数
本小节只是是为了通过演示如何动手写一个coroutine函数来了解其中的原理,实际久经考验的bluebird模块和co模块已经实现了此功能,下一小节将会讲解基于这些现成模块的使用方法。
首先我们需要了解一下 Generator 的概念。Generator 中文名称为“生成器”,通过function*来定义的函数称之为“生成器函数”(generator function),而生成器函数执行后返回的是一个生成器对象(Generator),这个生成器对象包含了几个方法,其中一个重要的方法是next(),我们可以通过不断地调用next()来取得在生成器中yield出来的值,生成器是否已执行结束则可以通过返回值的done属性来判断。
生成器有一个特点就是它可以中断函数的执行,每次执行yield语句之后,函数即暂停执行,直到调用返回的生成器对象的next()函数它才会继续执行。以下是一个简单的例子:
'use strict';
// 生成器函数,可以生成指定数量的数字
function* genNumbers(n) {
for (let i = 1; i &= i++) {
return 'ok';
// 执行生成器函数
const gen = genNumbers(10);
while (true) {
// 执行next()方法取下一个数字
const ret = gen.next();
// 打印结果
console.log(ret);
if (ret.done) {
// 如果done=true则表示生成器执行结束
console.log('done');
上面的代码执行后的结果如下:
{ value: 1, done: false }
{ value: 2, done: false }
{ value: 3, done: false }
{ value: 4, done: false }
{ value: 5, done: false }
{ value: 6, done: false }
{ value: 7, done: false }
{ value: 8, done: false }
{ value: 9, done: false }
{ value: 10, done: false }
{ value: 'ok', done: true }
每次执行next()都会返回一个包含{ value, done }两个属性的对象,其中value是该次yield返回的值,done表示是否执行结束
最后一次返回的值是生成器函数内return语句返回的值
从上文的代码可知,只有我们执行gen.next()时生成器才会继续执行。如果还不太确定,我们可以尝试把它换成异步的执行方式:
'use strict';
// 生成器函数,可以生成指定数量的数字
function* genNumbers(n) {
for (let i = 1; i &= i++) {
return 'ok';
// 执行生成器函数
const gen = genNumbers(10);
function next() {
// 执行next()方法取下一个数字
const ret = gen.next();
// 打印结果
console.log(ret);
if (ret.done) {
// 如果done=true则表示生成器执行结束
console.log('done');
// 500ms后继续执行
setTimeout(next, 500);
如无意外,执行上面的代码后我们应该能看到每隔 0.5 秒会打印出一行结果,直到 5 秒后程序才执行结束,而打印的结果跟之前的一模一样。
现在我们不妨假设,在我们的异步函数中,通过yield返回一个promise对象,然后等待promise执行回调后再执行gen.next()方法,如此循环,是不是就可以实现异步流程控制呢?
const ret = gen.next();
if (ret.done) {
// 执行结束
resolve(ret.value);
// 等待promise回调
.than(() =& ret.next())
.catch(err =& reject(err));
以下是这个简单coroutine函数的代码:
'use strict';
// 判断是否为Promise对象,再次只简单判断该对象是否包含then和catch方法
function isPromise(p) {
return p && typeof p.then === 'function' && typeof p.catch === 'function';
// coroutine函数,接收一个generator function作为参数,返回一个新的函数
function coroutine(genFn) {
return function () {
// 函数执行结果是一个promise对象
return new Promise((resolve, reject) =& {
// 首先执行generator function,它会返回一个Generator对象
const gen = genFn.apply(null, arguments);
function next(value) {
// 执行.next()返回yield返回的值
// next()可以接收一个参数,用作在生成器函数里面yield语句的返回值
ret = gen.next(value);
// 如果done=true则表示结束
if (ret.done) {
return resolve(ret.value);
// 如果返回的值不是promise则报错
if (!isPromise(ret.value)) {
return reject(new TypeError('You may only yield a promise, but the following object was passed: ' + String(ret.value)));
// 等待promise执行结果
ret.value.then(next).catch(reject);
// 开始执行
说明:此代码仅用作演示,尽管通常情况下它也能正确地运行,但是并没有考虑性能问题和一些异常情况,生产环境下请使用稳定的 NPM 模块。
使用 bluebird 模块的 coroutine 函数
使用前先执行以下命令安装bluebird模块:
$ npm install bluebird --save
以下是基于bluebird模块的coroutine函数的使用方法:
'use strict';
const Promise = require('bluebird');
const test = Promise.coroutine(function* (n, ms) {
for (let i = 0; i & i++) {
console.log('i=%s', i);
yield Promise.delay(ms);
test(10, 500)
.then(n =& console.log('执行结束,n=%s', n))
.catch(err =& console.error('执行出错:', err));
bluebird自带了delay()函数,功能与上文实现的sleep()相同
使用 co 模块
使用前先执行以下命令安装co模块:
$ npm install co --save
以下是基于co模块的简单使用方法:
'use strict';
const co = require('co');
function sleep(ms) {
return new Promise((resolve, reject) =& {
setTimeout(resolve, ms);
const test = co.wrap(function* (n, ms) {
for (let i = 0; i & i++) {
console.log('i=%s', i);
yield sleep(ms);
test(10, 500)
.then(n =& console.log('执行结束,n=%s', n))
.catch(err =& console.error('执行出错:', err));
实际上bluebird模块和co模块还是有区别的:bluebird模块只支持yield一个promise对象,而co模块可以支持promise,generator,array,object和Thunk函数,可在异步函数内实现多个并发异步任务,比前者复杂得多。
回想在过去的一年多时间里,我确实是对以使用generator的co模块来解决异步问题是有些许偏见,也曾喷过某月饼云的 Node.js SDK 竟然不支持callback而是直接返回一个generator。究其原因,我深以为有以下几点:
早期版本的co封装并不是返回一个promise对象,再加上大多数介绍co的文章讲的基本上都是thunks的概念,这对初使用co的人是相当恶心的
co 的yield支持的功能实在太丰fu富za了,而我更喜欢简单的
在 Node v4 发布之前,使用 Generator 还需要开启 Harmony 特性
从 Node v4 开始,直接支持了 Generator 和 Promise
最后一句,JavaScript 的世界变化实在太快了。
月饼云,这名字不错
我觉得想要“一步到位”(以后完全不改代码)的办法就是现在写async/await用Babel
自己机器调试用7.x版本的node
JS的世界更新太快,谁知道几个月后怎么样了呢
编译毕竟还是麻烦,理想情况还是直接用 Node.js 已经支持的语法来写。
自己机器跑7.X还好,反正最终提交代码CI在服务器上,本来也要走一个很长的flow不在意多一步
CNode 社区为国内最专业的 Node.js 开源技术社区,致力于 Node.js 的技术研究。
服务器赞助商为
,存储赞助商为
,由提供应用性能服务。
新手搭建 Node.js 服务器,推荐使用无需备案的

我要回帖

更多关于 promise js 异步 的文章

 

随机推荐