讲述Promise之前,先向大家推荐一个不错的关于Promise的学习资料JavaScript Promise迷你书(中文版)
var promise = new Promise(function(resolve, reject) { resolve("示例"); }); promise.then(function(value) { console.log(value); }, function(error) { console.log(error); });
Promise是抽象异步处理对象以及对其进行各种操作的组件。
getAsync("fileA.txt", function(error, result){ if(error){ // 取得失败时的处理 throw error; } // 取得成功时的处理 });
注意:Node.js等规定在JavaScript的回调函数的第一个参数为 Error 对象,这也是它的一个惯例。
var promise = new Promise(function(resolve, reject): { // 异步处理 // 处理结束后、调用resolve(成功) 或 reject(失败) }); promise.then(onFulfilled, onRejected)
(1)new Promise(function(resolve, reject):Promise构造器,返回一个promise对象 (2)resolve(成功):调用 onFulfilled (3)reject(失败):调用 onRejected 补充:Pending:promise对象刚被创建后的初始化状态 (4)promise.then(onFulfilled, onRejected):pomise对象会在变为resolve或者reject的时候分别调用相应注册的回调函数 等价于(promise.then的语法糖):
promise.then(function(value) { // onFulfilled 成功 }).catch(function(error){ // onRejected 失败 });
(5)promise.catch(function(error) {}):用来注册当promise对象状态变为Rejected时的回调函数;是 promise.then(undefined, onRejected);
方法的一个别名(语法糖)
注意:catch 是ECMAScript的 保留字 (Reserved Word)。基于ECMAScript 3实现的浏览器,会出现 identifier not found 这种语法错误;而在ECMAScript 5中保留字都属于 IdentifierName ,也可以作为属性名使用了。
为了回避ECMAScript保留字带来的问题,可以使用 promise["catch"](function(error) {})
如果需要支持IE8及以下版本的浏览器的话,那么一定要将这个 catch 问题牢记在心中!!!
function getURL(url) { return promise = new Promise(function(resolve, reject) { var 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(); }); } // 运行示例 getURL("http://httpbin.org/get").then(function onFulfilled(value) { console.log(value); }, function onRejected(error) { console.log(error); });
示例(1):
Promise.resolve("ligang").then(function(value) { console.log(value); });
可以认为是下述代码的语法糖:
new Promise(function(resolve) { resolve("ligang"); }).then(function(value) { console.log(value); });
示例(2):
Promise.reject("system error").catch(function(error) { console.error(error); });
可以认为是下述代码的语法糖:
new Promise(function(resolve, reject) { reject("system error"); }).then(null, function(error) { console.error(error); });
var promise = new Promise(function(resolve, reject){ console.log(1); resolve(2); }); promise.then(function(value) { // .then 中指定的方法调用是异步进行的 console.log(value); }); console.log(3); // 运行结果: 1 -> 3 -> 2
这个问题的本质是接收回调函数的函数,会根据具体的执行情况,可以选择是以同步还是异步的方式对回调函数进行调用。在开发中经常出现!!
function onReady(fn) { var readyState = document.readyState; if(readyState === 'interactive' || readyState === 'complete') { fn(); }else { window.addEventListener('DOMContentLoaded', fn); } } onReady(function() { console.log(1) }); console.log(2);
运行结果:
出现上述两种情况,往往会导致程序不能按预期执行。 (1)异步调用方式
function onReady(fn) { var readyState = document.readyState; if(readyState === 'interactive' || readyState === 'complete') { setTimeout(fn, 0); }else { window.addEventListener('DOMContentLoaded', fn); } } onReady(function() { console.log(1) }); console.log(2); // 运行结果统一输出:2 -> 1
(2)Promise重写
function onReadyPromise() { return new Promise(function(resolve, reject) { var readyState = document.readyState; if(readyState === 'interactive' || readyState === 'complete') { resolve(); }else { window.addEventListener('DOMContentLoaded', resolve); } }); } onReadyPromise().then(function(){ console.log(1); }).catch(function(){ console.error("error"); }); console.log(2); // 运行结果统一输出:2 -> 1
总结:
Promise里可以将任意个方法连在一起作为一个方法链
function taskA(value) { console.log("Task A"); return value = value + 1; } function taskB(value) { console.log("Tash B"); return value = value * 2; } function onRejected(error) { console.log("Catch Error:A or B", error); } function finalTask(value) { console.log("Final Tash", value); } var promise = Promise.resolve(1); // taskA、taskB的错误,都会被onRejected捕获 promise.then(taskA) .then(taskB) .catch(onRejected) .then(finalTask);
var promise = new Promise(function(resolve) { resolve(100); }); var thenPromise = promise.then(function(value) { console.log(value); }); var catchPromise = thenPromise.catch(function(error) { console.error(error); }); console.log(promise === thenPromise); // false console.log(thenPromise === catchPromise); // false
注意:在对Promise进行扩展的时候需要牢牢记住这一点,否则稍不留神就有可能对错误的promise对象进行了处理
// 错误使用 function badAsyncCall() { var promise = Promise.resolve(); promise.then(function() { // 任意处理 return newVar; }); return promise; }
// 正确使用,返回新的promise function anAsyncCall() { var promise = Promise.resolve(); return promise.then(function() { // 任意处理 return newVar; }); }
数组里的所有promise对象全部变为resolve或reject状态的时候,它才会去调用 .then 方法
function timerPromisefy(delay) { return new Promise(function(resolve, reject) { setTimeout(function(){ resolve(delay); }, delay); }); }
var startTime = Date.now(); Promise.all([ timerPromisefy(8), timerPromisefy(16), timerPromisefy(32) ]).then(function(result) { console.log(Date.now() - startTime); // 约32ms console.log(result); // [8, 16, 32] });
说明:Promise.all([])中的方法会同时开始执行(并行),而每个promise的结果和传递给Promise.all的promise数组的顺序是一致的。
数组里只要有一个promise对象变为resolve或reject状态的时候,就会去调用 .then 方法
function timerPromisefy(delay) { return new Promise(function(resolve, reject) { setTimeout(function(){ resolve(delay); }, delay); }); }
var startTime = Date.now(); Promise.race([ timerPromisefy(8), timerPromisefy(16), timerPromisefy(32) ]).then(function(result) { console.log(Date.now() - startTime); // 约8ms console.log(result); // 8 });
类Promise对象。拥有名为.then方法的对象。 jQuery.ajax(),它的返回值就是thenable对象。
Promise.resolve方法可以将Thenable对象转换为Promise对象。
var promise = Promise.resolve($.ajax('/json/comment.json')); // Thenable对象 => promise对象 promise.then(function(value){ console.log(value); });
在Promise类库之间进行相互转换是使用Thenable的最佳场景 例如:将ES6的promise对象转换为Q的promise的对象
var Q = require("Q"); // 这是一个ES6的promise对象 var promise = new Promise(function(resolve){ resolve(1); }); // 变换为Q promise对象 Q(promise).then(function(value){ console.log(value); }).finally(function(){ console.log("finally"); });
function copyOwnFrom(target, source) { Object.getOwnPropertyNames(source).forEach(function (propName) { Object.defineProperty(target, propName, Object.getOwnPropertyDescriptor(source, propName)); }); return target; } function TimeoutError() { var superInstance = Error.apply(null, arguments); copyOwnFrom(this, superInstance); } TimeoutError.prototype = Object.create(Error.prototype); TimeoutError.prototype.constructor = TimeoutError; function delayPromise(ms) { return new Promise(function (resolve) { setTimeout(resolve, ms); }); } function timeoutPromise(promise, ms) { var timeout = delayPromise(ms).then(function () { return Promise.reject(new TimeoutError('Operation timed out after ' + ms + ' ms')); }); return Promise.race([promise, timeout]); } function cancelableXHR(URL) { var req = new XMLHttpRequest(); var promise = new Promise(function (resolve, reject) { 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.onabort = function () { reject(new Error('abort this request')); }; req.send(); }); var abort = function () { // 如果request还没有结束的话就执行abort // https://developer.mozilla.org/en/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest if (req.readyState !== XMLHttpRequest.UNSENT) { req.abort(); } }; return { promise: promise, abort: abort }; } var object = cancelableXHR('http://httpbin.org/get'); // main timeoutPromise(object.promise, 1000).then(function (contents) { console.log('Contents', contents); }).catch(function (error) { if (error instanceof TimeoutError) { object.abort(); return console.log(error); } console.log('XHR Error :', error); });
Deferred和Promise不同,它没有共通的规范,每个Library都是根据自己的喜好来实现的。比如 jQuery.Deferred 和 JSDeferred 等
Deferred和Promise并不是处于竞争的关系,而是Deferred内涵了Promise。
function Deferred() { this.promise = new Promise(function(resolve, reject) { this._resolve = resolve; this._reject = reject; }.bind(this)); } Deferred.prototype.resolve = function(value) { this._resolve.call(this.promise, value); }; Deferred.prototype.reject = function(error) { this._reject.call(this.promise, error); };
function getURL(url) { var deferred = new Deferred(); var req = new XMLHttpRequest(); req.open('GET', url, true); req.onload = function() { if(req.status === 200) { deferred.resolve(req.responseText); }else { deferred.reject(new Error(req.statusText)); } } req.onerror = function() { deferred.reject(new Error(req.statusText)); } req.send(); return deferred.promise; } getURL("http://httpbin.org/get").then(function(value) { console.log(value); }).catch(function(error) { console.error(error); });
区别在于上述图中的“特权方法”。如上述,对比上述两个版本的 getURL: 1. Deferred 的话不需要将代码用Promise括起来,由于没有被嵌套在函数中,可以减少一层缩进 2. Deferred 没有Promise里的错误处理逻辑 3. Promise一般都会在构造函数中编写主要处理逻辑,对 resolve、reject 方法的调用时机也基本是很确定的
new Promise(function (resolve, reject){ // 在这里进行promise对象的状态确定 });
4. 而使用Deferred的话,并不需要将处理逻辑写成一大块代码,只需要先创建deferred对象,可以在任何时机对 resolve、reject 方法进行调用。
var deferred = new Deferred(); // 可以在随意的时机对 `resolve`、`reject` 方法进行调用
Promise代表了一个对象,这个对象的状态现在还不确定,但是未来一个时间点它的状态要么变为正常值(FulFilled),要么变为异常值(Rejected);而Deferred对象表示了一个处理还没有结束的这种事实,在它的处理结束的时候,可以通过Promise来取得处理结果。
本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。
我来说两句