同步编程通常来说易于调试和维护, 然而, 异步编程通常能获得更好的性能和更大的灵活性. 异步的最大特点是无需等待."Promises" 渐渐成为 JavaScript 里最重要的一部分, 大量的新 API 都开始 promise 原理实现. 下面让我们看一下什么是 promise, 以及它的 API 和用法!
Promises 现状
XMLHttpRequest API 是异步的, 但它没有使用 promise API. 但有很多原生的 JavaScript API 使用了 promise:
- Battery API
- fetch API (XHR 的替代品)
- ServiceWorker API
Promises 将来只会变得越来越流行, 普遍, 非常重要, 所有的前端开发人员都将用到它. 另一个值得注意的是, Node.JS 是基于 Promises 的平台(很显然, Promise 是它的一个核心特征).
Promises 的用法比你想象的要简单 -- 如果你以前喜欢使用 setTimeout 来控制异步任务的话!
Promise 基本用法
new Promise()构造器可以用在传统的异步任务中, 就像以前 setTimeout 和 XMLHttpRequest 的用法一样. 一个新的 Promise 使用 new 关键字生成, 同时, 这个 Promises 提供了 resolve 和 reject 函数让我们执行回调操作:
- 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 调用转换为 基于 Promises 的任务:
- // 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);
- });
Promise.resolve() 和 Promise.reject() 可以直接被调用. 有时候, 当判断出 promise 并不需要真正执行时, 我们并不需要 使用 new 创建 Promise 对象, 而是可以直接调用 Promise.resolve() 和 Promise.reject(). 比如:
- 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
then 回调动作的触发时机是 promise 被执行完. 我们还可以串联 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 已经执行完成, 单 then 被再次调用时, 回调动作将会被再次执行. 而如果这个 promise 里执行的是 reject 回调函数, 这是再调用 then 方法, 回调函数将不会被执行.
catch
catch 当一个 promise 被拒绝 (reject) 时, 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 方法里处理执行失败的结果, 而在 catch 里执行异常结果:
- reject(Error('Data could not be found'));
- Promise.all
在我们的异步调用时经常有这样一种场景: 我们需要同时调用多个异步操作, 但希望只有等所有的操作都完成后, 我们才去执行响应操作 -- 这就是 Promise.all 的作用. Promise.all 方法可以接收多个 promise 作为参数, 以数组的形式, 当这些 promise 都成功执行完成后才调用回调函数.
- 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!
- });
我们还可将 fetch 和电池状态 API 混合一起执行, 因为它们返回的都是 promise:
- Promise.all([fetch('/users.json'), navigator.getBattery()]).then(function(results) {
- // Both promises done!
- });
一旦 promise 里调用了 reject 函数, 也就是执行被拒绝了, 没有能够正常完成, 情况会有些复杂. 一旦 promise 被拒绝, catch 方法会捕捉到首个被执行的 reject 函数:
- 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.all 是非常重要的接口, 将会在很多新诞生的 promise API 中扮演重要的作用.
Promise.race
Promise.race 是一个有趣的函数 -- 它不是等待所有的 promise 被 resolve 或 reject, 而是在所有的 promise 中只要有一个执行结束, 它就会触发:
- 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!
一个有用的场景是, 从多个镜像服务器下载资源, 一旦有一个返回, 其它的返回也就不用处理了.
学会使用 Promises
Promises 在过去几年是一个非常火爆的话题, 它甚至从 JavaScript 里抽离出来变成了一个语言架构. 相信很快我们将见到有愈来愈多的 JavaScript API 将使用以 promise 为基础的模式.
来源: http://www.webhek.com/post/javascript-promise-api.html