当前位置: 移动技术网 > IT编程>开发语言>JavaScript > JS: Promise

JS: Promise

2019年02月26日  | 移动技术网IT编程  | 我要评论

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

备注


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

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网