JS: Promise

Promise

Promise是 JavaScript 异步编程中的重要概念,是目前较为流行的 JavaScript 异步编程解决方案之一。它最早由社区提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。

Promise 的基本概念


一个 Promise有以下几种状态:

  • pending: 初始状态,既不是成功,也不是失败状态(也可以说是进行中)。
  • fulfilled: 意味着操作成功完成。
  • rejected: 意味着操作失败。

pending 状态的 Promise 对象可能触发 fulfilled 状态并传递一个值给相应的状态处理方法,也可能触发失败状态(rejected)并传递失败信息。当其中任一种情况出现时,Promise 对象的 then 方法绑定的处理方法(handlers )就会被调用(then方法包含两个参数:onfulfilled 和 onrejected,它们都是 Function 类型。当Promise状态为 fulfilled 时,调用 then 的 onfulfilled 方法,当 Promise 状态为 rejected 时,调用 then 的 onrejected 方法, 所以在异步操作的完成和绑定处理方法之间不存在竞争)。 ------MDN

因为Promise.prototype.thenPromise.prototype.catch 方法会返回 promise 对象,所以它们可以被链式调用

Promise

Promise 的基本用法


// 这是 Promise 的常用方法
function promise(...) {
	return new Promise((resolve, reject) => {
		// do something
        if (/* fulfilled (异步操作成功) */) {
            return resolve(value);
        } 
	reject(error);
	});
}

promise(...).then((value) => {
    // success
}, (error) => {
    // failure
});

Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolvereject。它们是两个函数,由 JavaScript 引擎提供。

当状态从 pending变为fulfilled时,即异步操作成功,这时调用resolve,并将异步操作的结果,作为参数传递出去。当状态从 pending变为rejected时,即异步操作失败,这时调用reject,并将error作为参数传递出去。

Promise实例生成后,通过then方法部署fulfilled状态和rejected状态的回调函数。

下面是一个用Promise封装 Ajax 的例子:

const getJSON = function({
    // 解构赋值设置默认参数
    url = 'url', 
    method = 'GET', 
    async = false, 
    data = null
	} = {}) {
    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.open(url, method, async);
        xhr.responseType = 'json';
        xhr.setRequestHeader('Accept', 'application/json');
        xhr.onreadystatechange = function() {
            if (this.readyState == 4) {
                let status = this.status;
                if (status >= 200 && status < 300 || status == 304) {
                    resolve(this.response);
                } else {
                    reject(this.statusText);
				}
            }
        }
        xhr.send(data);
    });
}

getJSON({url: '/json'})
	.then(json => console.log(json), error => console.log(error));

Promise 的链式调用


以上面封装的 Ajax 为例子:

getJSON({url: '/json'})
.then( json => getJSON({url: json.oneUrl}) )
.then( json => getJSON({url: json.twoUrl}) )
.catch( error => {...} )
.then( () => {...} );

因为then或者catch方法会返回一个Promise对象,或者自己return一个Promise对象,而这样就可以继续使用then或者catch方法。

Promise.prototype.catch()


这里的catch()try/cath的方法类似,都是捕捉错误进行处理。

// 这两种写法是等价的
// 第一种
getJSON({url: '/json'}).then( (json) => {
	// success
}).catch( (error) => {
	// failure
});

// 第二种
getJSON({url: '/json'}).then((json) => {
    // success
},(error) => {
    // failure
});

但比较建议使用第一种写法,因为使用catch让代码更接近于同步代码,而且在链式调用中,使用catch方法可以捕捉前面所有的错误进行一并处理。

Promise对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止,而且catch还会返回一个Promise对象,这样可以继续使用then方法进行链式调用。

getJSON({url: '/json'})
.then( json => getJSON({url: json.oneUrl}) )
.then( json => getJSON({url: json.twoUrl}) )
.then( json => {...} )
.catch( error => {
    // 这里可以处理前面所有 Promise 的错误
}).then( () => {...} );

Promise.prototype.finally()


finally里面的操作不管 Promise 对象最后状态如何,都是会去执行的,这和 JAVA 的 finally 有点相似,即无论如何,一定执行。

promise(...)
.then(result => {···})
.catch(error => {···})
.finally(() => {
	// 这里的操作一定执行
});

值得注意的是,finally方法的回调函数是不接受任何参数的,这意味着没有办法知道前面的 Promise 状态到底是fulfilled还是rejected。所以finally方法里面的操作,应该是与状态无关的,不依赖于 Promise 的执行结果。

Promise.resolve()


Promise.resolve(value)方法返回一个以给定值解析后的promise对象。

但如果这个值是个 thenable(即带有then方法),返回的promise会“跟随”这个thenable的对象,采用它的最终状态(指resolved/rejected/pending/settled);

如果传入的value本身就是promise对象,则该对象作为Promise.resolve方法的返回值返回;

否则以该值为成功状态返回promise对象。

  • 传入参数是一个promise对象

    如果传入参数本身就是 Promise 实例,那么Promise.resolve不做任何修改、原封不动地返回这个实例。

    const p0 = new Promise((resolve, reject) => {
    	resolve(1);
    });
    const p1 = Promise.resolve(p0);
    console.log(p1 === p0); // true
    
  • 传入参数是thenable对象

    Promise.resolve()会将这个对象转为 Promise 对象,然后立即执行thenable对象的then方法,执行完毕之后,Promise 对象状态变为fulfilled状态,然后就会执行后续的then方法。

    // thenable 即带有 then 方法的对象
    let thenable = {
        then: (resolve, reject) => {
            resolve(1);
        }
    };
    const p2 = Promise.resolve(thenable);
    p2.then( value => console.log(value) );
    
  • 传入参数为其他值

    如果参数不为前面的两种的情况,则Promise.resolve方法会以fulfilled状态返回一个新的 Promise 对象,并将传入的参数传给then方法。

    const p3 = Promise.resolve(123);
    p3.then( value => console.log(value) ); // 123
    

Promise.reject()


Promise.reject(value)会以rejected状态返回一个 Promose 实例,并将传入参数原封不动地传给then或者catch方法。

const p4 = Promise.reject('错误');
// then()
p4.then(null, (err)=>{
        console.log(err); // 错误
    });
// catch()
p4.catch( err => console.log(err) ); // 错误

Promise.all()


Promise.all(iterable) 方法返回一个 Promise实例,此实例在 iterable 参数内所有的 promise 状态都为fulfilled状态(即成功)或参数中不包含 promise 时回调完成;如果参数中 promise 有一个状态为rejected状态(即失败)时回调失败,失败的原因是第一个失败 promise 的结果。

一般来说,Promise.all方法会传入一个数组参数,如果数组成员里有非Promise实例时,会自动调用上面所说的Promise.resolve方法将其转为Promise实例,再进行处理。

然后处理结果分两种情况:

1.参数内所有Promise实例的状态都为fulfilled状态,则返回一个包含所有resolve结果的数组传给后续的回调函数。

2.参数内有一个Promise实例的状态为rejected状态,则将第一个被rejectPromise实例的结果传给后续的回调函数。

const p5 = Promise.resolve(123);
const p6 = 42;
const p7 = new Promise((resolve, reject) => {
    setTimeout(resolve, 100, 'foo');
});

Promise.all([p5, p6, p7]).then((values) => {
    console.log(values); // [123, 42, "foo"]
});

Promise.race()


Promise.race方法与上面的Promise.all()类似,但不同的是,Promise.race()是只要传入的Promise实例中有一个状态改变了,就会直接返回这个最快改变状态的Promise实例的结果,而不是返回所有。

const p8 = Promise.resolve(123);
const p9 = new Promise((resolve, reject) => {
    setTimeout(resolve, 100, 'foo');
});

Promise.race([p8, p9]).then((values) => {
    // 这里只会获得 p8 的返回值
    console.log(values); // 123
});

手动实现 Promise.all()


有的面试会让实现Promise.all()方法,这个还算简单的,有的还要手撕Promise

首先来理清一下实现思路:

  • 遍历执行传入的Promise实例。

  • 如果传入的参数成员有不是Promise实例的,会直接调用Promise.resolve()进行处理。

  • 返回一个新的Promise实例,分两种情况返回结果。

  • fulfilled状态返回的数组结果要按顺序排列。

实现代码如下:

function promiseAll(promises) {
  // 返回一个新的 Promise 实例
  return new Promise((resolve, reject) => {
    let count = 0;
    let promisesNum = promises.length;
    let resolvedValue = new Array(promisesNum);
	for (let i=0; i<promisesNum; i++) {
        // 用 Promise.resolve() 处理传入参数
        Promise.resolve(promises[i])
           .then((value) => {
              // 顺序排列返回结果
              resolvedValue[i] = value;
              if (++count == promisesNum) {
                  return resolve(resolvedValue);
              }
            })
            .catch((err) => {
              // 返回第一个 rejected 状态的结果
              return reject(err);
        });
    }
  });
}

测试如下:

// 成功的情况
const p1=Promise.resolve(1);
const p2=Promise.resolve(2);
const p3=Promise.resolve(3);

promiseAll([p1, p2, p3]).then(console.log); // [1, 2, 3]
// 失败的情况
const p1=Promise.resolve(1);
const p2=Promise.reject('失败');
const p3=Promise.resolve(3);

promiseAll([p1, p2, p3])
  .then(console.log)
  .catch(console.log); // 失败

备注


春招开始了,有点不敢投,踌躇着,也焦虑着。

原文地址:https://www.cnblogs.com/guolao/p/10433577.html