JavaScript Promise API很棒,但是可以通过async
和await
变得惊人!
尽管同步代码更易于跟踪和调试,但异步通常在性能和灵活性方面更好。 当您一次触发多个请求,然后在每个请求准备就绪时处理它们时,为什么要“暂缓演出”呢? 承诺正成为JavaScript世界的重要组成部分,许多新的API都以promise理念实现。 让我们来看看Promise,API及其用法!
在野外的应许
当XMLHttpRequest API是异步,但不使用承诺API。 但是,现在有一些本机API使用Promise:
- 电池API
- 提取API (XHR的替代品)
- ServiceWorker API(即将发布帖子!)
承诺只会变得更加普遍,因此所有前端开发人员都必须习惯它们,这一点很重要。 还值得注意的是,Node.js是Promises的另一个平台(显然,Promise是核心语言功能)。
测试Promise可能比您想象的要容易,因为setTimeout
可以用作异步“任务”!
基本承诺用法
new Promise()
构造函数应仅用于旧式异步任务,例如setTimeout
或XMLHttpRequest
用法。 使用new
关键字创建一个新的Promise,并且promise为所提供的回调提供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(result) {
/* do something with the result */
}).catch(function() {
/* error :( */
}).finally(function() {
/* executes regardless or success for failure */
});
开发人员可以根据他们给定任务的结果,在回调的主体中手动调用“ 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);
});
有时候,你并不需要完成的承诺中异步任务-如果它可能是一个异步将采取行动,然而,返回承诺将最好的,让您可以随时在承诺未来数给定函数的出。 在这种情况下,您可以简单地调用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);
});
}
由于总是返回承诺,因此您可以始终使用then
和catch
方法获取其返回值!
然后
所有的Promise实例都有一个then
方法,该方法允许您对Promise做出React。 然后,第一个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
回调。 您还可以链接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
的返回值。
如果一个承诺已经解决,但then
再次被调用时,回调将立即触发。 如果承诺被拒绝和你打电话then
拒绝后,回调不会被调用。
抓住
当承诺被拒绝时,执行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
方法的取决于您自己。 常见的模式是将Error
发送给catch
:
reject(Error('Data could not be found'));
最后
无论成功与否,都会调用新引入的finally
回调:
(new Promise((resolve, reject) => { reject("Nope"); }))
.then(() => { console.log("success") })
.catch(() => { console.log("fail") })
.finally(res => { console.log("finally") });
// >> fail
// >> finally
Promise.all
考虑一下JavaScript加载器:有时候您会触发多个异步交互,但只想在所有异步交互完成后才进行响应-这就是Promise.all
进入的地方Promise.all
方法采用一组promises并触发一次回调他们都解决了:
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
和Battery API之类的API,因为它们都返回承诺:
Promise.all([fetch('/users.json'), navigator.getBattery()]).then(function(results) {
// Both promises done!
});
当然,处理拒绝是很难的。 如果任何承诺被拒绝,则第一个拒绝的catch
触发:
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: ', results);
}).catch(function(err) {
console.log('Catch: ', err);
});
// From the console:
// Catch: Second!
随着越来越多的API向Promise.all
将超级有用。
Promise.race
Promise.race
是一个有趣的函数Promise.race
无需等待所有诺言被解决或拒绝, Promise.race
在阵列中的任何诺言被解决或拒绝后Promise.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!
用例可能会触发对主要来源和次要来源的请求(如果主要来源或次要来源不可用)。
习惯承诺
在过去的几年中(或者,如果您是Dojo Toolkit用户,则是过去的十年),承诺一直是一个热门话题,并且它们已经从JavaScript框架模式变成了语言的主要内容。 假设您将看到大多数新JavaScript API正在使用基于承诺的模式来实现,这可能是明智的……
...那真是太好了! 开发人员可以避免回调地狱,并且可以像其他任何变量一样传递异步交互。 承诺需要一些时间来适应这些工具(本地),现在是时候学习它们了!
翻译自: https://davidwalsh.name/promises