JavaScript Promise API

尽管同步代码易于追踪和调试,但异步代码普遍在性能和灵活性上更具优势。Why "hold up the show" when you can trigger numerous requests at once and then handle them when each is ready?(这句要怎么翻??)promise和许多基于promise的新的API已经成为JavaScript世界重要的一部分。让我们来看一下promise的API如何来使用。

Promises in the Wild

XMLHttpRequest API是异步的但它并没有用Promises API,现在有一些native APIs正在使用promises:

  • Battery API(译者注:这篇文章我也有翻译

  • fetch API(XHR的取代者)

  • ServiceWorker API(关于这个API的文章正在路上)

promises会变得很流行,所以前端开发者们都应该去学习它。毫无疑问,Node.js是promises另一个重要的平台(显然,promises是一个核心语法特性)。

测试 promises 比你想得容易得多,因为 setTimeout 可以模拟你的异步“任务”

Basic Promise Usage

构造函数 new Promise() 应该只用于老式的异步任务,比如 setTimeout 或者 XMLHttpRequest。我们使用 new 关键字和promise提供的 resolvereject 回调函数来创建新的 promise:

var p = new Promise(function(resolve, reject) {
    
    // Do an async task async task and then...

    if(/* good condition */) {
        resolve('Success!');
    }
    else {
        reject('Failure!');
    }
});

p.then(function() { 
    /* do something with the result */
}).catch(function() {
    /* error :( */
})

根据异步任务的返回结果,开发者可以在回调函数体的内部手动调用 resolve 或者 reject。把 XMLHttpRequest 转化为基于promise的任务就是一个实际的例子:

// From Jake Archibald's Promises and Back:
// http://www.html5rocks.com/en/tutorials/es6/promises/#toc-promisifying-xmlhttprequest

function get(url) {
  // Return a new promise.
  return new Promise(function(resolve, reject) {
    // Do the usual XHR stuff
    var req = new XMLHttpRequest();
    req.open('GET', url);

    req.onload = function() {
      // This is called even on 404 etc
      // so check the status
      if (req.status == 200) {
        // Resolve the promise with the response text
        resolve(req.response);
      }
      else {
        // Otherwise reject with the status text
        // which will hopefully be a meaningful error
        reject(Error(req.statusText));
      }
    };

    // Handle network errors
    req.onerror = function() {
      reject(Error("Network Error"));
    };

    // Make the request
    req.send();
  });
}

// Use it!
get('story.json').then(function(response) {
  console.log("Success!", response);
}, function(error) {
  console.error("Failed!", error);
});

Sometimes you don't need to complete an async tasks within the promise -- if it's possible that an async action will be taken, however, returning a promise will be best so that you can always count on a promise coming out of a given function.(译者注:求大家帮忙翻译。。)这样你就可以简单地调用Promise.resolve() 或者 Promise.reject() 而不需要使用 new 关键字。举例说明:

var userCache = {};

function getUserDetail(username) {
  // In both cases, cached or not, a promise will be returned

  if (userCache[username]) {
    // Return a promise without the "new" keyword
    return Promise.resolve(userCache[username]);
  }

  // Use the fetch API to get the information
  // fetch returns a promise
  return fetch('users/' + username + '.json')
    .then(function(result) {
      userCache[username] = result;
      return result;
    })
    .catch(function() {
      throw new Error('Could not find user: ' + username);
    });
}

上面的函数总是返回一个promise对象,你总是可以对这个返回值使用then 或者 catch方法。

then

所有的promise实例都有 then 方法,来对这个promise实例做进一步处理。第一个 then 方法的回调函数接收 resolve() 方法里的值:

new Promise(function(resolve, reject) {
    // A mock async action using setTimeout
    setTimeout(function() { resolve(10); }, 3000);
})
.then(function(result) {
    console.log(result);
});

// From the console:
// 10

当promise被resolved时,会调用then回调方法。你也可以链式调用 then 方法:

new Promise(function(resolve, reject) { 
    // A mock async action using setTimeout
    setTimeout(function() { resolve(10); }, 3000);
})
.then(function(num) { console.log('first then: ', num); return num * 2; })
.then(function(num) { console.log('second then: ', num); return num * 2; })
.then(function(num) { console.log('last then: ', num);});

// From the console:
// first then:  10
// second then:  20
// last then:  40

每一个 then 方法接收到上一个 then 方法的返回值。

如果一个promise已经被resolvedthen方法有一次被调用,那么回调函数立刻执行。如果promise被rejected,而then方法在rejection之后,那回调函数永远不会被执行。

catch

当promise被rejected时,catch回调函数被执行:

new Promise(function(resolve, reject) {
    // A mock async action using setTimeout
    setTimeout(function() { reject('Done!'); }, 3000);
})
.then(function(e) { console.log('done', e); })
.catch(function(e) { console.log('catch: ', e); });

// From the console:
// 'catch: Done!'

reject方法里执行什么内容取决于你。一个常见的模式是发送一个Errorcatch方法中:

reject(Error('Data could not be found'));

Promise.all

想一下JavaScript loaders:有许多个异步任务被同时触发,但你只想在它们都完成之后才做出回应---这就是Promise.all的由来。Promise.all方法传入一个promise的数组,然后在它们全部resolved之后再出发回调函数:

Promise.all([promise1, promise2]).then(function(results) {
    // Both promises resolved
})
.catch(function(error) {
    // One or more promises was rejected
});

一个完美的想象Promise.all方法作用的例子就是一次性出发多个AJAX(通过 fetch):

var request1 = fetch('/users.json');
var request2 = fetch('/articles.json');

Promise.all([request1, request2]).then(function(results) {
    // Both promises done!
});

你可以合并都返回promise的APIs,比如fetch 和 Battery API:

Promise.all([fetch('/users.json'), navigator.getBattery()]).then(function(results) {
    // Both promises done!
});

当然,处理rejection是困难的。如果数组中有任意一个promise被rejectedcatch方法就被触发,并且接收第一个rejection

var req1 = new Promise(function(resolve, reject) { 
    // A mock async action using setTimeout
    setTimeout(function() { resolve('First!'); }, 4000);
});
var req2 = new Promise(function(resolve, reject) { 
    // A mock async action using setTimeout
    setTimeout(function() { reject('Second!'); }, 3000);
});
Promise.all([req1, req2]).then(function(results) {
    console.log('Then: ', one);
}).catch(function(err) {
    console.log('Catch: ', err);
});

// From the console:
// Catch: Second!

Promise.race

Promise.race是个有趣的方法---不是等待所有的promise被resolved或者rejected,而是只要数组中有一个promise被resolved或者rejectedPromise.race方法就被触发:

var req1 = new Promise(function(resolve, reject) { 
    // A mock async action using setTimeout
    setTimeout(function() { resolve('First!'); }, 8000);
});
var req2 = new Promise(function(resolve, reject) { 
    // A mock async action using setTimeout
    setTimeout(function() { resolve('Second!'); }, 3000);
});
Promise.race([req1, req2]).then(function(one) {
    console.log('Then: ', one);
}).catch(function(one, two) {
    console.log('Catch: ', one);
});

// From the console:
// Then: Second!

一个很有用的例子就是在有第一资源请求和第二资源请求时可以用Promise.race

Get Used to Promises

在过去的几年中,promise一直是一个热点话题(如果你是Dojo Toolkit用户的话,会是过去的10年间),现在promise已经从JavaScript框架特性的级别变成了JavaScript语言的特性。你将看到会有越来越多的API会基于promise来实现,这是一个很好的事情!开发者们可以避免以前的callback hell,并且异步interactions可以像其他的变量一样互相传递。promise花了很长的时间变成现在的样子,而现在是应该学习promise的时候了。

译自JavaScript Promise API

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容