欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

JS: Promise

程序员文章站 2022-04-23 17:20:11
Promise 是 JavaScript 异步编程中的重要概念,是目前较为流行的 JavaScript 异步编程解决方案之一。它最早由社区提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了 对象。 Promise 的基本概念 一个 有以下几种状态: pending: 初始状态,既不是成功 ......

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 对象,所以它们可以被链式调用

JS: 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); // 失败

备注


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