跳到主要内容

JavaScript Promise

Promise 指一种抽象的异步处理工作,其核心概念为 “确保在一件事完成之后,再做另一件事”。

基础​

Promise 规范的内容不多,简单描述如下:

  • 一个 promise 有三种状态:等待( pending ) 、 已完成( fulfilled ) 、 已拒绝( rejected )。
  • 一个 promise 对象只能从“等待”转到“完成”或者“拒绝”状态,不能逆转,同时“完成”和“拒绝状态不能相互转换”。
  • promise 必须使用 then() 方法。 then() 方法是 promise 的核心,而且 then 必须返回一个 promise 。同时,一个 promise 的 then 可以调用多次,并且回调函数的执行顺序和他们被定义时是一样的。
  • then() 方法接受两个参数, 1 个是成功的回调;另一个是失败的回调。同时, then 接受一个 promise 的传入,也接受一个 “类 then ”的对象和方法,即 thenable 对象。

创建 promise 对象​

const promise = new Promise(function (resolve, reject) {
/*
做一些事情 */ if (true) {
resolve('一切正常');
} else {
reject(Error('处理失败'));
}
});

Promise 类的构造函数有一个参数,参数作为回调函数。该参数又有两个参数,参数值为两个回调函数。

跟传统的 throw 一样,在调用 reject 时使用一个 Error 对象的这种方法只是习惯,而非必须。使用 Error 对象的好处就是他可以捕捉到一个错误堆栈,从而使调试工具变得更加有利。

promise 有一个 then() 方法(这字眼好像在哪见过...)...:

/* 只指定成功 */ promise.then(function (result) {
location.hash += result;
});
/_ 只指定失败_/;
promise.then(function (error) {
location.hash += err;
});

使用 then​

then 是可以链式使用,连续运行附加的异步操作:

/**
*
*/
function CreateXMLHTTP() {
if (window.ActiveXObject) {
var objXmlHttp = new ActiveXObject('Microsoft.XMLHTTP');
} else {
if (window.XMLHttpRequest) {
var objXmlHttp = new XMLHttpRequest();
} else {
alert('不能够初始化 XMLHTTP 对象!');
}
}
return objXmlHttp;
}
/**
*
*/
function read() {
const fileName = 'user.json';
const promise = new Promise(function (resolve, reject) {
const objXmlHttp = CreateXMLHTTP();
objXmlHttp.open('GET', fileName, true);
objXmlHttp.onreadystatechange = function () {
if (objXmlHttp.readyState == 4) {
if (objXmlHttp.status == 200) {
resolve(objXmlHttp.responseText);
} else reject();
}
};
objXmlHttp.send();
});
promise
.then(
function (response) {
return JSON.parse(response);
},
function () {
alert('读取失败');
},
)
.then(function (obj) {
document.getElementById('user').value = obj.user;
document.getElementById('password').value = obj.password;
});
}

列对化异步操作​

通过链式操作,调用 promise 对象的 then 方法可以实现列队的异步操作。当一个 then 方法的回调函数参数返回时,如果是一个值,下一个 then 则被立即调用,并且返回该值。然而,如果 then() 方法的回调参数返回一个 promise 对象,下一个 then() 方法将会对其等待,直到这个 promise 函数参数的处理结果确定后才被调用:

/**
*
*/
function CreateXMLHTTP() {
if (window.ActiveXObject) {
var objXmlHttp = new ActiveXObject('Microsoft.XMLHTTP');
} else {
if (window.XMLHttpRequest) {
var objXmlHttp = new XMLHttpRequest();
} else {
alert('不能够初始化 XMLHTTP 对象!');
}
}
return objXmlHttp;
}
/**
*
*/
function getData(fileName) {
return new Promise(function (resolve, reject) {
const objXmlHttp = CreateXMLHTTP();
objXmlHttp.open('GET', fileName, true);
objXmlHttp.onreadystatechange = function () {
if (objXmlHttp.readyState == 4) {
if (objXmlHttp.status == 200) {
allData += objXmlHttp.responseText + '';
resolve();
} else {
alert('读取文件失败');
}
}
};
objXmlHttp.send();
});
}
/**
*
*/
function read() {
allData = '';
getData('1.txt')
.then(function () {
return getData('2.txt');
})
.then(function () {
return;
getData('3.txt');
})
.then(function () {
const result = document.getElementById('result');
result.innerHTML = allData;
});
}

异常处理​

可以使用 catch() 来捕捉 Promise 构造函数的参数值中抛出的异常。

针对 then(func1,func2) 来说,只会出发其中一个回调函数,而 then(func1).catch(func2) 来说,两个都会被调用。

promise 对象的否定回调函数可以通过 promise.reject() 方法显式调用,也可以被 Promise 构造函数的参数值回调函数中抛出的异常错误隐式调用。

创建序列​

在使用 promise 对象序列时,需要用到 Promise 类的静态方法 resole() 。该方法最多可以拥有一个参数,当参数值为 promise 时, resolve() 方法传入的 promise 对象复制一个新的 promise 对象;如果传入其他的任何值, resolve() 方法将创建一个以这个值为肯定结果的 promise 对象;如果不指定参数值,将创建一个以 undefined 为肯结果的 promise 对象。

Promise 类存在一个最多可使用一个参数的 resolve() 静态方法,该方法创建一个 promise() 对象,该对象使用传入参数作为否定结果。如果不指定参数值,则创建一个 undefined 为否定的 promise 对象:

const result = document.getElementById('result');
const file = document.getElementById('file');
let allData = '';
/**
*
*/
function getData(file) {
return new Promise(function (resolve, reject) {
const reader = new FileReader(); // 将文件以文本形式进行读入页面
reader.readAsText(file);
reader.onload = function (f) {
allData += this.result + '';
resolve();
};
reader.onerror = function () {
reject();
};
});
}
/**
*
*/
function get(file) {
return getData(file).catch(function (err) {
alert('读取文件失败');
throw err;
});
}
/**
*
*/
function getSequence() {
const files = [];
for (let i = 0; i < document.getElementById('file').files.length; i++) {
files.push(document.getElementById('file').files[i]);
}
let sequence = Promise.resolve();
files.forEach(function (file) {
sequence = sequence.then(function () {
return get(file);
});
});
return sequence;
} // 将文件以文本形式进行读入页面
/**
*
*/
function read() {
Promise.resolve()
.then(function () {
return getSequence();
})
.then(function () {
const result = document.getElementById('result');
result.innerHTML = allData;
})
.catch(function () {
console.log('读取文件发生错误');
});
}

并行处理​

promise 的 all() 方法可以实现多个异步处理:

Promise.all(arrayOfPromise).then(function (arrayOfRequests) {
/_ some code_/;
});

例​

/**
*
*/
function CreateXMLHTTP() {
if (window.ActiveXObject) {
var objXmlHttp = new ActiveXObject('Microsoft.XMLHTTP');
} else {
if (window.XMLHttpRequest) {
var objXmlHttp = new XMLHttpRequest();
} else {
alert("Can't intialize XMLHTTP object !");
}
}
return objXmlHttp;
}
/**
*
*/
function getData(fileName) {
return new Promise(function (resolve, reject) {
const objXmlHttp = CreateXMLHTTP();
objXmlHttp.open('GET', fileName, true);
objXmlHttp.onreadystatechange = function () {
if (objXmlHttp.readyState == 4) {
if (objXmlHttp.status == 200) {
resolve(objXmlHttp.responseText);
} else {
reject();
}
}
};
objXmlHttp.send();
});
}
/**
*
*/
function read() {
Promise.all([getData('1.txt'), getData('2.txt'), getData('3.txt')]).then(
function (responses) {
const result = document.getElementById('result');
responses.forEach(function (response) {
result.innerHTML += response + '';
});
},
function () {
alert('读取文件失败');
},
);
}

在 getData 函数中创建返回的 Promise 对象。如果其中有任何一个对象返回否定的结果,那么都将触发 error 。

HTML 5 还提供了一个 Promise.race() 方法,该方法同样以一个 Promise 对象数组作为参数,但是数组中任何元素返回肯定结果时, Promise.race() 方法立即返回 肯定结果;或全部返回否定结果时,立即返回否定结果。