前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布

Promise

作者头像
奋飛
发布2019-08-15 09:56:34
1.4K0
发布2019-08-15 09:56:34
举报
文章被收录于专栏:Super 前端Super 前端

讲述Promise之前,先向大家推荐一个不错的关于Promise的学习资料JavaScript Promise迷你书(中文版)

代码语言:javascript
复制
var promise = new Promise(function(resolve, reject) {
    resolve("示例");
});
promise.then(function(value) {
    console.log(value);
}, function(error) {
    console.log(error);
});

一、什么是Promise

Promise是抽象异步处理对象以及对其进行各种操作的组件。

1. 回调函数的异步处理
代码语言:javascript
复制
getAsync("fileA.txt", function(error, result){
    if(error){
        // 取得失败时的处理
        throw error;
    }
    // 取得成功时的处理
});

注意:Node.js等规定在JavaScript的回调函数的第一个参数为 Error 对象,这也是它的一个惯例。

2. Promise简介
代码语言:javascript
复制
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的语法糖):

代码语言:javascript
复制
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 问题牢记在心中!!!

图 promise-states.png
图 promise-states.png
图 promise-onFulfilled_onRejected.png
图 promise-onFulfilled_onRejected.png
3. 编写promise代码,把XHR处理包装
代码语言:javascript
复制
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);
});

二、实战Promise

1. 创建promise对象
  • (1)new Promise(function(resolve, reject) {});
  • (2)Promise.resolve(value)
  • (3)Promise.reject(error)

示例(1):

代码语言:javascript
复制
Promise.resolve("ligang").then(function(value) {
    console.log(value);
});

可以认为是下述代码的语法糖:

代码语言:javascript
复制
new Promise(function(resolve) {
    resolve("ligang");
}).then(function(value) {
    console.log(value);
});

示例(2):

代码语言:javascript
复制
Promise.reject("system error").catch(function(error) {
    console.error(error);
});

可以认为是下述代码的语法糖:

代码语言:javascript
复制
new Promise(function(resolve, reject) {
    reject("system error");
}).then(null, function(error) {
    console.error(error);
});
2. Promise只能异步操作?
代码语言:javascript
复制
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
3. 同步调用和异步调用同时存在导致的混乱

这个问题的本质是接收回调函数的函数,会根据具体的执行情况,可以选择是以同步还是异步的方式对回调函数进行调用。在开发中经常出现!!

代码语言:javascript
复制
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);

运行结果:

  • 如果在调用onReady之前DOM已经载入的话,对回调函数进行同步调用。输出:1 -> 2
  • 如果在调用onReady之前DOM还没有载入的话,通过注册 DOMContentLoaded 事件监听器来对回调函数进行异步调用。输出:2 -> 1

出现上述两种情况,往往会导致程序不能按预期执行。 (1)异步调用方式

代码语言:javascript
复制
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重写

代码语言:javascript
复制
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

总结:

  1. 绝对不能对异步回调函数(即使在数据已经就绪)进行同步调用。
  2. 如果对异步回调函数进行同步调用的话,处理顺序可能会与预期不符,可能带来意料之外的后果。
  3. 对异步回调函数进行同步调用,还可能导致栈溢出或异常处理错乱等问题。
  4. 如果想在将来某时刻调用异步回调函数的话,可以使用 setTimeout 等异步API。
4. promise chain

Promise里可以将任意个方法连在一起作为一个方法链

代码语言:javascript
复制
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); 
图 promise-then-catch-flow.png
图 promise-then-catch-flow.png
5. then、catch返回新的promise对象
代码语言:javascript
复制
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对象进行了处理

代码语言:javascript
复制
// 错误使用
function badAsyncCall() {
    var promise = Promise.resolve();
    promise.then(function() {
        // 任意处理
        return newVar;
    });
    return promise;
}
代码语言:javascript
复制
// 正确使用,返回新的promise
function anAsyncCall() {
    var promise = Promise.resolve();
    return promise.then(function() {
        // 任意处理
        return newVar;
    });
}
6. Promise.all([]).then(function(result) {})

数组里的所有promise对象全部变为resolve或reject状态的时候,它才会去调用 .then 方法

代码语言:javascript
复制
function timerPromisefy(delay) {
    return new Promise(function(resolve, reject) {
        setTimeout(function(){
            resolve(delay);
        }, delay);
    });
}
代码语言:javascript
复制
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数组的顺序是一致的。

7. Promise.race()

数组里只要有一个promise对象变为resolve或reject状态的时候,就会去调用 .then 方法

代码语言:javascript
复制
function timerPromisefy(delay) {
    return new Promise(function(resolve, reject) {
        setTimeout(function(){
            resolve(delay);
        }, delay);
    });
}
代码语言:javascript
复制
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
});

补充:Thenable

1. 什么是Thenable

类Promise对象。拥有名为.then方法的对象。 jQuery.ajax(),它的返回值就是thenable对象。

2. 将Thenable转换为Promise对象

Promise.resolve方法可以将Thenable对象转换为Promise对象。

代码语言:javascript
复制
var promise = Promise.resolve($.ajax('/json/comment.json'));  // Thenable对象 => promise对象
promise.then(function(value){
   console.log(value);
});
3. 什么时候该使用Thenable

在Promise类库之间进行相互转换是使用Thenable的最佳场景 例如:将ES6的promise对象转换为Q的promise的对象

代码语言:javascript
复制
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");
});

三、使用Promise.race和delay取消XHR请求

1. 通过超时取消XHR操作
代码语言:javascript
复制
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

1. 什么是Deferred

Deferred和Promise不同,它没有共通的规范,每个Library都是根据自己的喜好来实现的。比如 jQuery.Deferred 和 JSDeferred 等

2. Deferred和Promise关系

Deferred和Promise并不是处于竞争的关系,而是Deferred内涵了Promise。

3. 基于Promise实现Deferred
代码语言:javascript
复制
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);
};
4. 示例【实现上述XHR包裹】
代码语言:javascript
复制
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);
});
5. Deferred和Promise区别
deferred-and-promise.png
deferred-and-promise.png

区别在于上述图中的“特权方法”。如上述,对比上述两个版本的 getURL: 1. Deferred 的话不需要将代码用Promise括起来,由于没有被嵌套在函数中,可以减少一层缩进 2. Deferred 没有Promise里的错误处理逻辑 3. Promise一般都会在构造函数中编写主要处理逻辑,对 resolve、reject 方法的调用时机也基本是很确定的

代码语言:javascript
复制
new Promise(function (resolve, reject){
    // 在这里进行promise对象的状态确定
}); 

4. 而使用Deferred的话,并不需要将处理逻辑写成一大块代码,只需要先创建deferred对象,可以在任何时机对 resolve、reject 方法进行调用。

代码语言:javascript
复制
var deferred = new Deferred(); // 可以在随意的时机对
    `resolve`、`reject` 方法进行调用

Promise代表了一个对象,这个对象的状态现在还不确定,但是未来一个时间点它的状态要么变为正常值(FulFilled),要么变为异常值(Rejected);而Deferred对象表示了一个处理还没有结束的这种事实,在它的处理结束的时候,可以通过Promise来取得处理结果。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2016年05月15日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、什么是Promise
    • 1. 回调函数的异步处理
      • 2. Promise简介
        • 3. 编写promise代码,把XHR处理包装
        • 二、实战Promise
          • 1. 创建promise对象
            • 2. Promise只能异步操作?
              • 3. 同步调用和异步调用同时存在导致的混乱
                • 4. promise chain
                  • 5. then、catch返回新的promise对象
                    • 6. Promise.all([]).then(function(result) {})
                      • 7. Promise.race()
                      • 补充:Thenable
                        • 1. 什么是Thenable
                          • 2. 将Thenable转换为Promise对象
                            • 3. 什么时候该使用Thenable
                            • 三、使用Promise.race和delay取消XHR请求
                              • 1. 通过超时取消XHR操作
                              • 补充:Deferred
                                • 1. 什么是Deferred
                                  • 2. Deferred和Promise关系
                                    • 3. 基于Promise实现Deferred
                                      • 4. 示例【实现上述XHR包裹】
                                        • 5. Deferred和Promise区别
                                        领券
                                        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档