ECMAScript6 Promise对象

362 阅读11分钟

Promise对象:异步编程

Promise 表示一个异步操作的最终结果。以下几个术语的含义。

  • 解决(fulfill):指一个 promise 成功时进行的一系列操作,如状态的改变、回调的执行。虽然规范中用 fulfill 来表示解决,但在后世的 promise 实现多以 resolve 来指代之。
  • 拒绝(reject):指一个 promise 失败时进行的一系列操作。
  • 终值(eventual value):所谓终值,指的是 promise 被解决时传递给解决回调的值,由于 promise 有一次性的特征,因此当这个值被传递时,标志着 promise 等待态的结束,故称之终值,有时也直接简称为值(value)。
  • 据因(reason):也就是拒绝原因,指在 promise 被拒绝时传递给拒绝回调的值。

Promise有三种状态:

pending(进行中)、fulfilled(已成功)和rejected(已失败)

Promise对象有以下两个特点

(1)对象状态不受外界影响:只有异步操作的结果,可以决定当前的状态。

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

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

pending变为fulfilled(resolved) 需携带一个value值。

pending变为rejected 需携带一个reason值

当这两种情况发生后,状态定型,再取调用promise对象,也只会得到和之前一样的结果。

Promise也有一些缺点

  • 无法取消状态:一旦新建就会立即执行,无法中途取消。

  • 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部;也就是使用

  • 当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

基础语法:

创建一个promise实例:

const promise = new Promise(function(resolve, reject) {
  // ... some code

  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

//resolve: 将结果由pendding转为resolved,即”未完成“转”成功“;
// reject: 将结果由pendding转为rejected,即”未完成“转”失败“

Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。

promise.then(function(value) {
  // success resolve的回调
}, function(error) {
  // failure  reject的回调
});

例:一旦promise实例新建,就会被立即执行

let promise = new Promise(function(resolve, reject) {
  console.log('Promise');
  resolve();
});

promise.then(function() {
  console.log('resolved.');
});

console.log('Hi!');

// Promise 
// Hi!
// resolved

注意,调用resolvereject并不会终结 Promise 的参数函数的执行。

new Promise((resolve, reject) => {
  resolve(1);
  console.log(2);
}).then(r => {
  console.log(r);
});
// 2
// 1

例:使用promise实现Ajax的功能:

const getJson = function (url) {
    const promise = new Promise((resolve, reject) => {
         var handler = function () {
             if (this.readyState !== 4) {
                 return;
             }
             if (this.status === 200) {
                 resolve(this.response);
             } else {
                 reject(new Error(this.statusText));
             }
         }
         const client = new XMLHttpRequest();
         client.open("GET", url);
         client.onreadystatechange = handler;
         client.responseType = "json";
         client.setRequestHeader("Accept", "application/json");
         client.send();
     });     
    return promise;
}
getJson("url.json")
.then(result => {
         console.log("success:" + result);
}).catch(error => {
         console.log("error:" + error);
});

Promise.prototype.then()

一个 promise 必须提供一个 then 方法以访问其当前值、终值和据因。

promise 的 then 方法接受两个参数:

promise.then(onFulfilled, onRejected)

参数可选

onFulfilledonRejected 都是可选参数。

  • 如果 onFulfilled 不是函数,其必须被忽略
  • 如果 onRejected 不是函数,其必须被忽略

onFulfilled 特性

如果 onFulfilled 是函数:

  • promise 执行结束后其必须被调用,其第一个参数为 promise 的终值
  • promise 执行结束前其不可被调用
  • 其调用次数不可超过一次

onRejected 特性

如果 onRejected 是函数:

  • promise 被拒绝执行后其必须被调用,其第一个参数为 promise 的据因
  • promise 被拒绝执行前其不可被调用
  • 其调用次数不可超过一次

调用时机

onFulfilledonRejected 只有在执行环境堆栈仅包含平台代码时才可被调用 

调用要求

onFulfilledonRejected 必须被作为函数调用(即没有 this 值)

多次调用

then 方法可以被同一个 promise 调用多次

  • promise 成功执行时,所有 onFulfilled 需按照其注册顺序依次回调
  • promise 被拒绝执行时,所有的 onRejected 需按照其注册顺序依次回调

返回

then 方法必须返回一个 promise 对象。**所以可以采用链式写法。**即then方法后面再调用另一个then方法。达到可以将前一个then实例的返回结果作为后一个实例的参数传入。

const promise1 = new  Promise((resolve, reject) => { 
    // dosomething...
})
const promise2= promise1.then(onFulfilled, onRejected);
  • 如果 onFulfilled 或者 onRejected 返回一个值 x ,则运行下面的 Promise 解决过程[[Resolve]](promise2, x)
  • 如果 onFulfilled 或者 onRejected 抛出一个异常 e ,则 promise2 必须拒绝执行,并返回拒因 e
  • 如果 onFulfilled 不是函数且 promise1 成功执行, promise2 必须成功执行并返回相同的值
  • 如果 onRejected 不是函数且 promise1 拒绝执行, promise2 必须拒绝执行并返回相同的据因

不论 promise1 被 reject 还是被 resolve 时 promise2都会被 resolve,只有出现异常时才会被 rejected

Promise 解决过程resolve

Promise 解决过程是一个抽象的操作,其需输入一个 promise 和一个值,我们表示为 [[Resolve]](promise, x),如果 xthen 方法且看上去像一个 Promise ,解决程序即尝试使 promise 接受 x 的状态;否则其用 x 的值来执行 promise

这种thenable的特性使得 Promise 的实现更具有通用性:只要其暴露出一个遵循 Promise/A+ 协议的 then 方法即可;这同时也使遵循 Promise/A+ 规范的实现可以与那些不太规范但可用的实现能良好共存。

运行 [[Resolve]](promise, x) 需遵循以下步骤:

x 与 promise 相等

如果 promisex 指向同一对象,以 TypeError 为据因拒绝执行 promise

x 为 Promise

如果 x 为 Promise ,则使 promise 接受 x 的状态 :

  • 如果 x 处于等待态, promise 需保持为等待态直至 x 被执行或拒绝
  • 如果 x 处于执行态,用相同的值执行 promise
  • 如果 x 处于拒绝态,用相同的据因拒绝 promise

x 为对象或函数

如果 x 为对象或者函数:

  • x.then 赋值给 then 
  • 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise
  • 如果 then 是函数,将 x 作为函数的作用域 this 调用之。传递两个回调函数作为参数,第一个参数叫做 resolvePromise ,第二个参数叫做 rejectPromise:
    • 如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y)
    • 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
    • 如果 resolvePromiserejectPromise 均被调用,或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用
    • 如果调用 then 方法抛出了异常 e
      • 如果 resolvePromiserejectPromise 已经被调用,则忽略之
      • 否则以 e 为据因拒绝 promise
    • 如果 then 不是函数,以 x 为参数执行 promise
  • 如果 x 不为对象或者函数,以 x 为参数执行 promise

如果一个 promise 被一个循环的thenable链中的对象解决,而 [[Resolve]](promise, thenable) 的递归性质又使得其被再次调用,根据上述的算法将会陷入无限递归之中。算法虽不强制要求,但也鼓励施者检测这样的递归是否存在,若检测到存在则以一个可识别的 TypeError 为据因来拒绝 promise

function resolve(promise, x) {
    if (x === promise) { //promise和x指向相同的值
        return reject(promise, new TypeError("cant be the same"));
    } if (isPromise(x)) { // x是一个promise
                if (x.state === "pending") {
            return x.then(() => {
                resolve(promise, x.value);
            }, () => {
                reject(promise, x.value)
            })
        } if (x.state === "rejected") {
            return reject(promise, x.value)
        } if (x.state === "fulfilled") {
            return fulfill(promise, x.value);
        }
    } else if (isObject(x) || isFunction(x)) { // x是一个对象或一个函数
        let then;
        try {
            then = x.then;
        } catch (e) {
            return reject(promise, x.value)
        }
        if (isFunction(then)) {
            let iscalled = false;
            try {
                then.call(x, function resolvePromise(y) { //对thenable进行兼容
                    if (iscalled) {
                        return;
                    }
                    iscalled = true; 
                    resolve(promise, y);
                }, function rejectPromise(r) {
                    if (iscalled) {
                        return;
                    }
                    iscalled = true;
                    reject(promise, r);
                })
            } catch (e) {
                if (!iscalled) {
                    return reject(promise, e);
                }
            }
        } else {
            return fulfill(promise, x);
        }
    } else { //x不是对象也不是函数
         return fulfill(promise, x);
   }
 }

示例1

const promise = new Promise((resolve, reject)=>{
    console.log(1);
    resolve();
    console.log(2);
});
promise.then(()=>{
    console.log(3);
});
console.log(4);//输出结果 1  2  4  3

示例2

const promise = Promise.resolve(1) //设置为resolve状态,并且值为1
.then(2) // 当传入值不是一个函数, 则返回上一个promise的值1,及状态resolve
.then(Promise.resolve(3)) // 是一个promise,则返回上一个promise的值1,及状态resolve
.then(console.log) //是一个function, 执行function,所以输出1

//输出1

Promise.prototype.catch()

Promise.prototype.catch()方法是.then(null, rejection).then(undefined, rejection)的别名,用于指定发生错误时的回调函数。, 处理前一个回调函数的reject

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

如果 Promise 状态已经变成**resolved**,再抛出错误是无效的。

const promise = new Promise(function(resolve, reject) {
  resolve('ok');
  throw new Error('test');
});
promise
  .then(function(value) { console.log(value) })
  .catch(function(error) { console.log(error) });
// ok

Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个**catch**语句捕获。

getJSON('/post/1.json').then(function(post) {
  return getJSON(post.commentURL);
}).then(function(comments) {
  // some code
}).catch(function(error) {
  // 处理前面三个Promise产生的错误, 前面三个promise产生的错误都会被这一个catch捕获
});

catch语法捕获后面的then链式语句产生的错误,catch也能抛出错误:

const someAsyncThing = function() {
  return new Promise(function(resolve, reject) {
    // 下面一行会报错,因为x没有声明
    resolve(x + 2);
  });
};

someAsyncThing().then(function() {
  return someOtherAsyncThing();
}).catch(function(error) {
  console.log('oh no', error);
  // 下面一行会报错,因为 y 没有声明, 但是不会被捕获,因为接下来没有catch语句, 也不会传递到外层
  y + 2;
}).then(function() {
  console.log('carry on');
});
// oh no [ReferenceError: x is not defined]

Promise.prototype.finally()

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

**finally**方法的回调函数不接受任何参数,无法得知promise的状态, 所以这里买呢的操作一般都是与状态无关的擦偶哦在,不依赖promise执行结果的一些操作。

promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});

和Java的 try catch finally一样

实现:

Promise.prototype.finally = function (callback) {
  let P = this.constructor;
  return this.then(
    value  => P.resolve(callback()).then(() => value),
    reason => P.resolve(callback()).then(() => { throw reason })
  );
};

从上面的实现还可以看到,finally方法总是会返回原来的值。

// resolve 的值是 undefined
Promise.resolve(2).then(() => {}, () => {})

// resolve 的值是 2
Promise.resolve(2).finally(() => {})

// reject 的值是 undefined
Promise.reject(3).then(() => {}, () => {})

// reject 的值是 3
Promise.reject(3).finally(() => {})

Promise.all()

Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。

const p = Promise.all([p1, p2, p3]);
//p1,p2,p3都是promise实例

p的状态由p1p2p3决定,分成两种情况。

(1)只有p1p2p3的状态都变成fulfilledp的状态才会变成fulfilled,此时p1p2p3的返回值组成一个数组,传递给p的回调函数。

(2)只要p1p2p3之中有一个被rejectedp的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。

注意,如果作为参数的 Promise 实例,自己定义了catch方法,那么它一旦被rejected,并不会触发Promise.all()catch方法。

const p1 = new Promise((resolve, reject) => {
  resolve('hello');
})
.then(result => result)
.catch(e => e);

const p2 = new Promise((resolve, reject) => {
  throw new Error('报错了');
})
.then(result => result)
.catch(e => e);

Promise.all([p1, p2])
.then(result => console.log(result)) // ["hello", Error: 报错了]
.catch(e => console.log(e));


const p1 = new Promise((resolve, reject) => {
  resolve('hello');
})
.then(result => result);

const p2 = new Promise((resolve, reject) => {
  throw new Error('报错了');
})
.then(result => result);

Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));// Error: 报错了

Promise.race()

Promise.race()方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。

const p = Promise.race([p1, p2, p3]);

只要p1p2p3之中有一个实例率先改变状态,**p**的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数

例:如果指定时间内没有获得结果,就将 Promise 的状态变为reject,否则变为resolve

const p = Promise.race([
  fetch('/resource-that-may-take-a-while'),
  new Promise(function (resolve, reject) {
    setTimeout(() => reject(new Error('request timeout')), 5000)
  })
]);

p.then(console.log).catch(console.error);
 //表示五秒之后还没有结果,就直接返回reject,从而触发catch函数

Promise.allSettled()

Promise.allSettled()方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例

只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束。

结束的状态只可能是fulfilled, 可以根据返回的结果对象种的status来判断此promise实例的状态。

该方法返回的新的 Promise 实例,一旦结束,状态总是**fulfilled**,不会变成**rejected**。状态变成fulfilled后,Promise 的监听函数接收到的参数是一个数组。每个成员对应一个传入Promise.allSettled()的 Promise 实例。

const resolved = Promise.resolve(42);
const rejected = Promise.reject(-1);

const allSettledPromise = Promise.allSettled([resolved, rejected]);

allSettledPromise.then(function (results) {
  console.log(results);
});
// [
//    { status: 'fulfilled', value: 42 },
//    { status: 'rejected', reason: -1 }
// ]

**常用场景:**有时又对于多个异步请求,我们不关心这些请求的操作结果, 只关心所有的操作是否都完成。

Promise.any()

Promise.any()方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。

只要参数实例有一个变成**fulfilled**状态包装实例就会变成**fulfilled**状态

如果所有参数实例都变成**rejected**状态,包装实例就会变成rejected状态;

**Promise.any()**抛出的错误,不是一个一般的错误,而是一个 AggregateError 实例。它相当于一个数组,每个成员对应一个被**rejected**的操作所抛出的错误

这跟promise.all()相反。

var resolved = Promise.resolve(42);
var rejected = Promise.reject(-1);
var alsoRejected = Promise.reject(Infinity);

Promise.any([resolved, rejected, alsoRejected]).then(function (result) {
  console.log(result); // 42
});

Promise.any([rejected, alsoRejected]).catch(function (results) {
  console.log(results); // [-1, Infinity]
});

Promise.resolve():有时需要将现有对象转为 Promise 对象

Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))

const jsPromise = Promise.resolve($.ajax('/whatever.json')); 
// 将jquery生成的对象转化为新的promise对象

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

(1)参数是一个 Promise 实例

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

(2)参数是一个thenable对象

thenable对象指的是具有then方法的对象。

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

let thenable = {
  then: function(resolve, reject) {
    resolve(42);
  }
};

let p1 = Promise.resolve(thenable);
p1.then(function(value) {
  console.log(value);  // 42
});

(3)参数不是具有then方法的对象,或根本就不是对象

如果参数是一个原始值,或者是一个不具有then方法的对象,**Promise.resolve**方法返回一个新的 Promise 对象,状态为resolved。将传入参数作为resolved的结果传出。

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

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

(4)不带有任何参数:适用于只得到一个promise对象

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

const p = Promise.resolve();

p.then(function () {
  // ...
});

注意:立即resolve()的 Promise 对象,是在本轮“事件循环”(event loop)的结束时执行,而不是在下一轮“事件循环”的开始时。

setTimeout(function () {
  console.log('three');
}, 0); //在下一轮“事件循环”开始时执行

Promise.resolve().then(function () {
  console.log('two');
}); //在本轮“事件循环”结束时执行

console.log('one'); //立即执行
 
// one
// two
// three

Promise.reject()

Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected

const p = Promise.reject('出错了');
// 等同于
const p = new Promise((resolve, reject) => reject('出错了'))

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

注意:Promise.reject()方法的参数会原封不动地作为**reject**的理由,变成后续方法的参数。

const thenable = {
  then(resolve, reject) {
    reject('出错了');
  }
};

Promise.reject(thenable)
.catch(e => {
  console.log(e === thenable)
})
// true

Promise使用场景

一般使用promise的链式调用可以轻松解决“回调地狱”。

对于回调函数 我们用Jquery的ajax获取数据时 都是以回调函数方式获取的数据

$.get(url, (data) => {
    console.log(data)
)

如果说当我们需要发送多个异步请求 并且每个请求之间需要相互依赖 那这时 我们只能 以嵌套方式来解决 形成 "回调地狱"

$.get(url, data1 => {
    console.log(data1)
    $.get(data1.url, data2 => {
        console.log(data2)
        $.get(data2.url, data3 => {
           console.log(data3)
        })
    })
})

利用Promise 处理多个相互关联的异步请求

const request = url => { 
    return new Promise((resolve, reject) => {
        $.get(url, data => {
            resolve(data)
        });
    })
};

// 请求data1
request(url).then(data1 => {
    return request(data1.url);   
}).then(data2 => {
    return request(data2.url);
}).then(data3 => {
    console.log(data3);
}).catch(err => throw new Error(err));

参考文章:Promise 对象