混子前端所知道关于ES6的Promise

栏目: JavaScript · 发布时间: 7年前

内容简介:Promise是异步编程的一种解决方案,比传统的解决方案 [ 回调函数和事件] 更合理也更强大。混子前端认为 Promise 简单来说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。Promise对象有以下两个特点:

Promise是异步编程的一种解决方案,比传统的解决方案 [ 回调函数和事件] 更合理也更强大。

Promise的含义

混子前端认为 Promise 简单来说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。

Promise对象有以下两个特点:

  • 对象的状态不受外界影响:

    Promise代表一个异步操作,有三种状态:

    Pending:进行中

    Resolved:已完成,又称Fulfilled

    Rejected:已失败

    只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。

  • 一旦状态改变,就不会再变,任何时候都可以得到这个结果。

    Promise对象的状态改变,只有两种可能:

    1. 从Pending变为Resolved

    2. 从Pending变为Rejected

    只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。

有了Promise对象就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操作更加容易。

当然混子前端认为Promise也有一些缺点:

  • 无法取消Promise,一旦新建它就会立即执行,无法中途取消。
  • 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。
  • 当处于Pending状态时,无法得知目前进展到哪一个阶段。

基本用法

ES6规定,Promise对象是一个构造函数,用来生成Promise实例,来看代码:

JS code:

var promise = new Promise(function(resolve, reject) {
    // ... some code
	
    if (/* 异步操作成功 */) {
        resolve(value);
    } else {
	reject(error);
    }
});

复制代码
说明:

Promise构造函数接受一个函数作为参数,该函数的两个参数分别是 resolve 和 reject。

  • resolve函数:将Promise对象的状态从 “未完成” 变为 ”成功“ (即从Pending变为Resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;
  • reject函数:将Promise对象的状态从 “未完成” 变为 “失败” (即从Pending变为Rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

Promise实例生成以后,可以用then方法分别指定Resolved状态和Reject状态的回调函数,来看代码:

JS code:

promise.then(function(value) {

    // success

}, function(error) {

    // failure

});

复制代码

说明: then方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为Resolved时调用,第二个回调函数是Promise对象的状态变为Reject时调用。其中,第二个函数是可选的,不一定要提供。

利用Promise我们可以模拟很多场景,混子前端来用Promise实现我们项目中常用 异步加载图片和 实现Ajax操作。

下面是异步加载图片的例子,来看代码:

JS code:

function loadImageAsync(url) {
    return new Promise(function(resolve, reject) {
        var image = new Image();
        image.onload = function() {
            resolve(image);
        };

        image.onerror = function() {
            reject(new Error('Could not load image at ' + url));
        };

        image.src = url;
    });
}
 复制代码

说明: 使用Promise包装了一个图片加载的异步操作。如果加载成功,就调用 resolve 方法,否则就调用 reject 方法。

接下来是一个用Promise对象实现Ajax操作的例子,来看代码:

JS code:

var getJSON = function(url) {
    var promise = new Promise(function(resolve, reject){
        var client = new XMLHttpRequest();
        client.open("GET", url);
        client.onreadystatechange = handler;
        client.responseType = "json";
        client.setRequestHeader("Accept", "application/json");
        client.send();
    
        function handler() {
            if (this.readyState !== 4) {
                return;
            }
            if (this.status === 200) {
                resolve(this.response);
            } else {
                reject(new Error(this.statusText));
            }
        };
    });
    return promise;
};

getJSON("/posts.json").then(function(json) {
    console.log('Contents: ' + json);
}, function(error) {
    console.error('出错了', error);
});
 复制代码

说明: getJSON 是对 XMLHttpRequest 对象的封装,用于发出一个针对JSON数据的HTTP请求,并且返回一个Promise对象。

Promise.prototype.then()

Promise实例具有 then 方法,then 方法是定义在原型对象Promise.prototype上的。

它的作用是为Promise实例添加状态改变时的回调函数。

then方法接受两个参数:

  • Resolved状态的回调函数
  • (可选)是Rejected状态的回调函数

then 方法返回的是一个新的Promise实例(ps:不是原来那个Promise实例),因此可以采用链式写法,即 then 方法后面再调用另一个 then 方法,来看代码:

JS code:

getJSON("/posts.json").then(function(json) {
    return json.post;
}).then(function(post) {
    // ...
}); 复制代码

说明: 代码使用then方法,依次指定了两个回调函数,第一个回调函数完成以后,会将返回结果作为参数,传入第二个回调函数。

采用链式的 then ,可以指定一组按照次序调用的回调函数。这时,前一个回调函数,有可能返回的还是一个Promise对象(即有异步操作),这时后一个回调函数,就会等待该Promise对象的状态发生变化,才会被调用。

接下来看一下可选参数的回调函数,来看代码:

JS code:

getJSON("/post/1.json").then(function(post) {
    return getJSON(post.commentURL);
}).then(function funcA(comments) {
    console.log("Resolved: ", comments);
}, function funcB(err){
    console.log("Rejected: ", err);
});
复制代码
说明:

第一个 then 方法指定的回调函数,返回的是另一个Promise对象。这时,第二个 then 方法指定的回调函数,就会等待这个新的Promise对象状态发生变化。如果变为Resolved,就调用 funcA 函数,如果状态变为Rejected,就调用 funcB 函数。

Promise.prototype.catch()

Promise.prototype.catch 方法是 .then(null, rejection) 的别名,用于指定发生错误时的回调函数,来看代码:

JS code:

getJSON("/posts.json").then(function(posts) {
    // ...
}).catch(function(error) {
    // 处理 getJSON 和 前一个回调函数运行时发生的错误
    console.log('发生错误!', error);
});
复制代码

说明:getJSON 方法返回一个Promise对象,如果该对象状态变为   Resolved  ,则会调用  then 方法指定的回调函数;如果异步操作抛出错误,状态就会变为   Rejected  ,就会调用   catch  方法指定的回调函数,处理这个错误。另外, then  方法指定的回调函数,如果运行中抛出错误,也会被   catch  方法捕获。

Promise.all()

Promise.all 方法用于将多个Promise实例,包装成一个新的Promise实例,来看代码:

JS code:

var p = Promise.all([p1, p2, p3]);
复制代码

说明: Promise.all  方法接受一个数组作为参数,p1、p2、p3  都是Promise对象的实例,如果不是,就会先调用下面讲到的  Promise.resolve  方法,将参数转为Promise实例,再进一步处理。 Promise.all  方法的参数可以不是数组,但必须具有Iterator接口,且返回的每个成员都是Promise实例。

这里p的状态由p1、p2、p3 决定,分成两种情况:

  • 只有 p1、p2、p3 的状态都变成 fulfilled,p 的状态才会变成   fulfilled ,此时的返回值组成p1、p2、p3 一个数组,传递给 p 的回调函数。
  • 只要 p1、p2、p3 之中有一个被   rejected,p 的状态就变成   rejected,此时第一个被reject的实例的返回值,会传递给 p 的回调函数。

来看例子:

JS code:

// 生成一个Promise对象的数组
var promises = [2, 3, 5, 7, 11, 13].map(function (id) {
    return getJSON("/post/" + id + ".json");
});

Promise.all(promises).then(function (posts) {
    // ...
}).catch(function(reason){
    // ...
});
复制代码

说明: promise 是 包含6个Promise实例的数组,只有这6个实例的状态都变成  fulfilled ,或者其中有一个变为   rejected ,才会调用  Promise.all  方法后面的回调函数。

Promise.race()

Promise.race 方法同样是将多个Promise实例,包装成一个新的Promise实例,来看代码:

JS code:

var p = Promise.race([p1, p2, p3]);复制代码

说明: 只要 p1、p2、p3  之中有一个实例率先改变状态, 状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给  p 的回调函数。

Promise.race 方法的参数与 Promise.all 方法一样,如果不是 Promise 实例,就会调用Promise.resolve 方法,将参数转为 Promise 实例,再做处理。

Promise.resolve()

有时需要将现有对象转为Promise对象,Promise.resolve 方法就起到这个作用,来看例子:

JS code:

var jsPromise = Promise.resolve($.ajax('/whatever.json'));
复制代码

说明: 上面代码将jQuery生成的 deferred 对象,转为一个新的Promise对象。

Promise.resolve 方法的参数分成四种情况。

  • 参数是一个Promise实例    

    如果参数是Promise实例,Promise.resolve 将不做任何修改、原封不动地返回这个实例。
  • 参数是一个 thenable 对象

    thenable 对象指的是具有 then 方法的对象,来看代码:
JS code:

let thenable = {
    then: function(resolve, reject) {
        resolve(2);
    }
};
复制代码

     说明:  Promise.resolve 方 法会将这个对象转为 Promise 对象,然后就立即执行   thenable  对象的  then  方法。

  • 参数不是具有 

    then 方法的对象,或根本就不是对象

    如果参数是一个原始值,或者是一个不具有 then 方法的对象,则 Promise.resolve方法返回一个新的Promise对象,状态为 Resolved,来看代码:
JS code:

var p = Promise.resolve('Hello');

p.then(function (s){
  console.log(s)
});

// Hello复制代码

    说明: 字符串 “ Hello”  不属于异步操作,返回Promise实例的状态从一生成就是  Resolved  ,所以回调函数会立即执行。

  • 不带有任何参数

    Promise.resolve 方法允许调用时不带参数,直接返回一个 Resolved 状态的Promise对象。

Promise.reject()

Promise.reject(reason) 方法也会返回一个新的Promise实例,该实例的状态为 rejected 。它的参数用法与 Promise.resolve 方法完全一致,来看代码:

JS code:

var p = Promise.reject('出错了');

// 等同于:
var p = new Promise((resolve, reject) => reject('出错了'))

p.then(null, function (s){
    console.log(s)
});
// 出错了
复制代码

说明: 生成一个Promise对象的实例p ,状态为  rejected ,回调函数会立即执行。

两个有用的附加方法

说明: Promise对象不管以  then   方法或  catch   方法结尾,要是最后一个方法抛出错误,都有可能无法捕捉到(因为Promise内部的错误不会冒泡到全局)因此,提供一个  done  方法,总是处于回调链的尾端,保证抛出任何可能出现的错误,来看代码:

JS code:

Promise.prototype.done = function (onFulfilled, onRejected) {
    this.then(onFulfilled, onRejected)
        .catch(function (reason) {
      	    // 抛出一个全局错误
            setTimeout(() => { throw reason }, 0);
    	});
};
复制代码

说明: done 方法的使用,可以像  then  方法那样用,提供  Fulfilled   和  Rejected   状态的回调函数,也可以不提供任何参数。但不管怎样  done  都会捕捉到任何可能出现的错误,并向全局抛出。

finally()

finally 方法用于指定不管Promise对象最后状态如何,都会执行的操作。

与 done 方法的最大区别:它接受一个普通的回调函数作为参数,该函数必须执行。

来看代码:

JS code:

server.listen(0)
    .then(function () {
       // run test
    })
    .finally(server.stop);

复制代码

手写Promise

文章的最后,给大家手写一个不管是面试还是技术探讨都会经常提到的手写promise,其实总结上面相信大家也都能写出来,来看代码:

JS code:

function myPromise(constructor){
    let self = this;
    self.status = 'pending';	// 定义状态改变前的初始状态
    self.value = undefined;    // 定义状态为resolved时候的状态
    self.reason = undefined;    // 定义状态为rejected时候的状态

    function resolve(value) {
        if(self.status === 'pending'){    // 两个函数判断等于pending, 保证了状态的改变是不可逆的
	    self.value = value;
	    self.status = 'resolved';
        }
    }

    function reject(reason) {
        if(self.status === 'pending'){    // 两个函数判断等于pending, 保证了状态的改变是不可逆的
	    self.value = reason;
	    self.status = 'rejected';
	}
    }

    // 捕获构造异常
    try{
        constructor(resolve, reject);
    }catch{    
        reject(e);
    }

}

myPromise.prototype.then = function(onFullfilled, onRejected){
    let self = this;
    switch(self.status){
    case 'resolved':
        onFullfilled(self.value);
        break;
    case 'rejected':
        onRejected(self.reason);
	break;
    }
}


var p = new myPromise(function(resolve, reject){
    resolve(1)
});

p.then(function(x){
    console.log(x)
});
复制代码

好了,以上就是混子前端总结关于Promise的知识点,欢迎点赞和纠错。

最后祝大家工作日愉快!

内心os:还有两天休息才能愉快 )


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

The Apache Modules Book

The Apache Modules Book

Nick Kew / Prentice Hall PTR / 2007-02-05 / USD 54.99

"Do you learn best by example and experimentation? This book is ideal. Have your favorite editor and compiler ready-you'll encounter example code you'll want to try right away. You've picked the right......一起来看看 《The Apache Modules Book》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

在线进制转换器
在线进制转换器

各进制数互转换器

MD5 加密
MD5 加密

MD5 加密工具