前端开发中常常会进行一些异步操做,常见的异步有:javascript
博客地址php
最基础的异步解决方案莫过于回调函数了html
前端常常会在成功时和失败时分别注册回调函数前端
const req = new XMLHttpRequest(); req.open('GET', URL, true); req.onload = function () { // 成功的回调 if (req.status === 200) { console.log(req.statusText) } }; req.onerror = function () { // 失败的回调 console.log(req.statusText) }; req.send();
node的异步api,则一般只注册一个回调函数,经过约定的参数来判断究竟是成功仍是失败:java
const fs = require("fs"); fs.readFile('input.txt', function (err, data) { // 回调函数 // 第一个参数是err,若是有err,则表示调用失败 if (err) { return console.error(err); } console.log("异步读取: " + data.toString()); });
回调的异步解决方案自己也简单易懂,可是它有一个致命的缺点:没法优雅的控制异步流程node
什么意思?git
单个异步固然能够很简单的使用回调函数,可是对于多个异步操做,就会陷入回调地狱中es6
// 请求data1成功后再请求data2,最后请求data3 const ajax = $.ajax({ url: 'data1.json', success: function(data1) { console.log(data1); $.ajax({ url: 'data2.json', success: function(data2) { console.log(data2); $.ajax({ url: 'data3.json', success: function(data3) { console.log(data3); } }) } }) } })
这种要按顺序进行异步流程控制的场景,回调函数就显得捉襟见肘了。这时,Promise的异步解决方案就被提了出来。github
当初在学Promise时,看得我真是一脸懵逼,彻底不明白这货到底怎么用。其实,Promise的api要分红两部分来理解:ajax
Promise对象表明一个异步操做,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)
初始时,该对象状态为pending,以后只能变成fulfilled和rejected其中的一个
then方法有两个参数,分别对应状态为fulfilled和rejected时的回调函数,其中第二个参数可选
promise.then(function(value) { // success }, function(error) { // failure });
一般咱们会省略then的第二个参数,而改用catch来注册状态变为rejected时的回调函数
promise.then(function(value) { // success }).catch(function(error) { // failure });
Promise对象怎么生成的呢?就是经过构造函数new出来的。
const promise = new Promise(function(resolve, reject) { });
Promise构造函数接收一个函数做为参数,这个函数能够接收两个参数:resolve和reject
resolve, reject是两个函数,由JavaScript引擎提供,不用本身编写
前面咱们说过,Promise对象有三种状态,初始时为pending,以后能够变成fulfilled或者rejected,那怎么改变状态呢?答案就是调用resolve或者reject
调用resolve时,状态变成fulfilled,表示异步已经完成;调用reject时,状态变成rejected,表示异步失败。
其实这里就是Promise最难理解的地方了,咱们先看下例子:
回调函数封装
function getURL(URL, success, error) { const req = new XMLHttpRequest(); req.open('GET', URL, true); req.onload = function () { if (req.status === 200) { success(req.responseText); } else { error(new Error(req.statusText)); } }; req.onerror = function () { error(new Error(req.statusText)); }; req.send(); } const URL = "http://httpbin.org/get"; getURL(URL, function onFulfilled(value) { console.log(value); }, function onRejected(error) { console.error(error); })
Promise封装
function getURL(URL) { return new Promise(function (resolve, reject) { const req = new XMLHttpRequest(); req.open('GET', URL, true); req.onload = function () { if (req.status === 200) { resolve(req.responseText); } else { reject(new Error(req.statusText)); } }; req.onerror = function () { reject(new Error(req.statusText)); }; req.send(); }); } const URL = "http://httpbin.org/get"; getURL(URL).then(function onFulfilled(value){ console.log(value); }).catch(function onRejected(error){ console.error(error); });
两段代码最大的区别就是:
用回调函数封装的getURL函数,须要明显的传给它成功和失败的回调函数,success和error的最终调用是在getURL里被调用的
用Promise封装的getURL函数,彻底不关心成功和失败的回调函数,它只须要在ajax成功时调用resolve(),告诉promise对象,你如今的状态变成了fulfilled,在ajax失败时,调用reject()。而真正的回调函数,是在getURL的外面被调用的,也就是then和catch中调用
then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。所以能够采用链式写法,即then方法后面再调用另外一个then方法。
function getURL(URL) { return new Promise(function (resolve, reject) { const req = new XMLHttpRequest(); req.open('GET', URL, true); req.onload = function () { if (req.status === 200) { resolve(req.responseText); } else { reject(new Error(req.statusText)); } }; req.onerror = function () { reject(new Error(req.statusText)); }; req.send(); }); } const URL = "http://httpbin.org/get"; const URL2 = "http://deepred5.com/cors.php?search=ntr"; getURL(URL).then(function onFulfilled(value){ console.log(value); // 返回了一个新的Promise对象 return getURL(URL2) }).then(function onFulfilled(value){ console.log(value); }).catch(function onRejected(error){ console.error(error); });
这段代码就充分说明了Promise对于流程控制的优点:读取URL的数据后再读取URL2,没有了以前的回调地狱问题。
Promise常常用于对函数的异步流程封装
function getURL(URL) { return new Promise(function (resolve, reject) { const req = new XMLHttpRequest(); req.open('GET', URL, true); req.onload = function () { if (req.status === 200) { resolve(req.responseText); } else { reject(new Error(req.statusText)); } }; req.onerror = function () { reject(new Error(req.statusText)); }; req.send(); }); }
const preloadImage = function (path) { return new Promise(function (resolve, reject) { const image = new Image(); image.onload = resolve; image.onerror = reject; image.src = path; }); };
const fs = require('fs') const path = require('path') const readFilePromise = function (fileName) { return new Promise((resolve, reject) => { fs.readFile(fileName, (err, data) => { if (err) { reject(err) } else { resolve(data.toString()) } }) }) }
结合上面几个例子,咱们能够看出Promise封装代码的基本套路:
const methodPromise = function() { return new Promise((resolve, reject) => { // 异步流程 if (/* 异步操做成功 */){ resolve(value); } else { reject(error); } }) }
Promise.all 接收一个promise对象的数组做为参数,当这个数组里的全部promise对象所有变为resolve的时候,它才会去调用then方法,若是其中有一个变为rejected,就直接调用catch方法
传给then方法的是一个数组,里面分别对应promise返回的结果
function getURL(URL) { return new Promise(function (resolve, reject) { const req = new XMLHttpRequest(); req.open('GET', URL, true); req.onload = function () { if (req.status === 200) { resolve(req.responseText); } else { reject(new Error(req.statusText)); } }; req.onerror = function () { reject(new Error(req.statusText)); }; req.send(); }); } Promise.all([getURL('http://deepred5.com/cors.php?search=ntr'), getURL('http://deepred5.com/cors.php?search=rbq')]) .then((dataArr) => { const [data1, data2] = dataArr; }).catch((err) => { console.log(err) })
Promise.race相似,只不过只要有一个Promise变成resolve就调用then方法
Promise.resolve(42); // 等价于 new Promise(function(resolve){ resolve(42); }); Promise.reject(new Error("出错了")) // 等价于 new Promise(function(resolve,reject){ reject(new Error("出错了")); });
Promise.resolve(42).then(function(value){ console.log(value); }); Promise.reject(new Error("出错了")).catch(function(error){ console.error(error); });
Promise.resolve方法另外一个做用就是将thenable对象转换为promise对象
const promise = Promise.resolve($.ajax('/json/comment.json'));// => promise对象 promise.then(function(value){ console.log(value); });
thenable对象指的是具备then方法的对象:
let thenable = { then: function(resolve, reject) { resolve(42); } }; let p1 = Promise.resolve(thenable); p1.then(function(value) { console.log(value); // 42 });
理想状态下,Promise能够经过catch捕获到异常,可是若是咱们没有使用catch,那么虽然控制台会打印错误,可是此次错误并不会终止脚本执行
<script> const a = b.c.d; console.log(1); // 代码报错,不会运行到此处 </script> <script> console.log(2); // 代码运行 </script>
上述代码只会打印2
<script> const promise = new Promise((resolve, reject) => { const a = b.c.d; resolve('ok'); }) promise.then(data => { console.log(data) }) console.log(1); // 代码报错,可是会运行到此处 </script> <script> console.log(2); // 代码运行 </script>
打印1和2
解决方法:
window有一个unhandledRejection事件,专门监听未捕获的reject错误
window.onunhandledrejection = function(e) { console.log(e.reason); } const promise = new Promise((resolve, reject) => { const a = b.c.d; resolve('ok'); }) promise.then(data => { console.log(data) })