ES6-promise(阮一峰ES6读后总结)

1、Promise的含义

Promise是异步编程的一种解决方案,语法上来讲是一个对象,从它能够获取异步操做的消息。Promise 提供统一的 API,各类异步操做均可以用一样的方法进行处理,使得控制异步操做更加容易 。javascript

Promise对象的两个特色: java

一、对象的状态不受外界影响ajax

  • Promise对象表明一个异步操做,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)
  • 只有异步操做的结果,能够决定当前是哪种状态,任何其余操做都没法改变这个状态

二、一旦状态改变,就不会再变,任什么时候候均可以获得这个结果编程

  • Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected
  • 只要这两种状况发生,状态就不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)
  • 若是改变已经发生了,你再对Promise对象添加回调函数,也会当即获得这个结果

注意,为了行文方便,本章后面的resolved统一只指fulfilled状态,不包含rejected状态json

优势:c#

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

缺点:api

  • 没法取消Promise,一旦新建它就会当即执行,没法中途取消
  • 若是不设置回调函数,Promise内部抛出的错误,不会反应到外部
  • 当处于pending状态时,没法得知目前进展到哪个阶段(刚刚开始仍是即将完成)

2、基本用法

ES6 规定,Promise对象是一个构造函数,用来生成Promise实例。数组

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

Promise构造函数接受一个函数做为参数,该函数的两个参数分别是resolvereject。它们是两个函数,由 JavaScript 引擎提供,不用本身部署。promise

resolve函数的做用:Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操做成功时调用,并将异步操做的结果,做为参数传递出去浏览器

reject函数的做用:将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操做失败时调用,并将异步操做报出的错误,做为参数传递出去。

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

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

下面为Promise对象的简单例子:timeout方法返回一个Promise实例,表示一段时间之后才会发生的结果。过了指定的时间(ms参数)之后,Promise实例的状态变为resolved,就会触发then方法绑定的回调函数。

function timeout(ms) {
  return new Promise((resolve, reject) => {
    setTimeout(resolve, ms, 'done');
  });
}
timeout(100).then((value) => {
  console.log(value);
});

下面代码中,Promise 新建后就会当即执行,因此首先输出的是Promise。而后,then方法指定的回调函数,将在当前脚本全部同步任务执行完才会执行,因此resolved最后输出。

let promise = new Promise(function(resolve, reject) {
  console.log('Promise');
  resolve();
});
promise.then(function() {
  console.log('resolved.');
});
console.log('Hi!');
// Promise
// Hi!
// resolved

若是调用resolve函数和reject函数时带有参数,那么它们的参数会被传递给回调函数。reject函数的参数一般是Error对象的实例,表示抛出的错误;resolve函数的参数除了正常的值之外,还多是另外一个 Promise 实例,好比:

const p1 = new Promise(function (resolve, reject) {
  // ...
});
const p2 = new Promise(function (resolve, reject) {
  // ...
  resolve(p1);
})

上面代码中,p1p2都是 Promise 的实例,可是p2resolve方法将p1做为参数,即一个异步操做的结果是返回另外一个异步操做。

注意,这时p1的状态就会传递给p2,也就是说,p1的状态决定了p2的状态。若是p1的状态是pending,那么p2的回调函数就会等待p1的状态改变;若是p1的状态已是resolved或者rejected,那么p2的回调函数将会马上执行。

const p1 = new Promise(function (resolve, reject) {
  setTimeout(() => reject(new Error('fail')), 3000)
})

const p2 = new Promise(function (resolve, reject) {
  setTimeout(() => resolve(p1), 1000)
})

p2
  .then(result => console.log(result))
  .catch(error => console.log(error))
// Error: fail

上面代码中,p1是一个 Promise,3 秒以后变为rejectedp2的状态在 1 秒以后改变,resolve方法返回的是p1。因为p2返回的是另外一个 Promise,致使p2本身的状态无效了,由p1的状态决定p2的状态。因此,后面的then语句都变成针对后者(p1)。又过了 2 秒,p1变为rejected,致使触发catch方法指定的回调函数。

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

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

上面代码中,调用resolve(1)之后,后面的console.log(2)仍是会执行,而且会首先打印出来。这是由于当即 resolved 的 Promise 是在本轮事件循环的末尾执行,老是晚于本轮循环的同步任务。

通常来讲,调用resolvereject之后,Promise 的使命就完成了,后继操做应该放到then方法里面,而不该该直接写在resolvereject的后面。因此,最好在它们前面加上return语句,这样就不会有意外。

new Promise((resolve, reject) => {
  return resolve(1);
  // 后面的语句不会执行
  console.log(2);
})

3、Promise.prototype.then() 

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

then方法的做用:为 Promise 实例添加状态改变时的回调函数。

then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。所以能够采用链式写法,即then方法后面再调用另外一个then方法。(第一个回调函数完成之后,会将返回结果做为参数,传入第二个回调函数)

getJSON("/post/1.json").then(function(post) {
  return getJSON(post.commentURL);
}).then(function (comments) {
  console.log("resolved: ", comments);
}, function (err){
  console.log("rejected: ", err);
});

若是采用箭头函数,上面的代码能够写得更简洁。

getJSON("/post/1.json").then(
  post => getJSON(post.commentURL)
).then(
  comments => console.log("resolved: ", comments),
  err => console.log("rejected: ", err)
);

4、Promise.prototype.catch()

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

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

上面代码中,getJSON()方法返回一个 Promise 对象,若是该对象状态变为resolved,则会调用then()方法指定的回调函数;若是异步操做抛出错误,状态就会变为rejected,就会调用catch()方法指定的回调函数,处理这个错误。另外,then()方法指定的回调函数,若是运行中抛出错误,也会被catch()方法捕获。

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

通常来讲,不要在then()方法里面定义 Reject 状态的回调函数(即then的第二个参数),老是使用catch方法。

// bad
promise
  .then(function(data) {
    // success
  }, function(err) {
    // error
  });

// good
promise
  .then(function(data) { //cb
    // success
  })
  .catch(function(err) {
    // error
  });

上面代码中,第二种写法要好于第一种写法,理由是第二种写法能够捕获前面then方法执行中的错误,也更接近同步的写法(try/catch)。所以,建议老是使用catch()方法,而不使用then()方法的第二个参数。

跟传统的try/catch代码块不一样的是,若是没有使用catch()方法指定错误处理的回调函数,Promise 对象抛出的错误不会传递到外层代码,即不会有任何反应。

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

someAsyncThing().then(function() {
  console.log('everything is great');
});

setTimeout(() => { console.log(123) }, 2000);
// Uncaught (in promise) ReferenceError: x is not defined
// 123

上面代码中,someAsyncThing()函数产生的 Promise 对象,内部有语法错误。浏览器运行到这一行,会打印出错误提示ReferenceError: x is not defined,可是不会退出进程、终止脚本执行,2 秒以后仍是会输出123。这就是说,Promise 内部的错误不会影响到 Promise 外部的代码,通俗的说法就是“Promise 会吃掉错误”。

5、Promise.prototype.finally()

finally()方法用于指定无论 Promise 对象最后状态如何,都会执行的操做。finally方法里面的操做,是与状态无关的,不依赖于 Promise 的执行结果。

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

finally本质上是then方法的特例

promise
.finally(() => {
  // 语句
});

// 等同于
promise
.then(
  result => {
    // 语句
    return result;
  },
  error => {
    // 语句
    throw error;
  }
);

实现:

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 })
  );
};

6、Promise.all()

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

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

Promise.all()方法的参数能够不是数组,但必须具备 Iterator 接口,且返回的每一个成员都是 Promise 实例。

p的状态由p1p2p3决定,分红两种状况:

  • 只有p1p2p3的状态都变成fulfilledp的状态才会变成fulfilled,此时p1p2p3的返回值组成一个数组,传递给p的回调函数。
  • 只要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))
.catch(e => console.log(e));
// ["hello", Error: 报错了]

7、Promise.race()

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

8、Promise.allSettled()

Promise.allSettled()方法接受一组 Promise 实例做为参数,包装成一个新的 Promise 实例。只有等到全部这些参数实例都返回结果,无论是fulfilled仍是rejected,包装实例才会结束。

const promises = [
  fetch('/api-1'),
  fetch('/api-2'),
  fetch('/api-3'),
];

await Promise.allSettled(promises);
removeLoadingIndicator();

上面代码对服务器发出三个请求,等到三个请求都结束,无论请求成功仍是失败,加载的滚动图标就会消失。

有时候,咱们不关心异步操做的结果,只关心这些操做有没有结束。这时,Promise.allSettled()方法就颇有用。

9、Promise.any()

Promise.any()方法接受一组 Promise 实例做为参数,包装成一个新的 Promise 实例。只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;若是全部参数实例都变成rejected状态,包装实例就会变成rejected状态。

Promise.any()Promise.race()方法很像,只有一点不一样,就是不会由于某个 Promise 变成rejected状态而结束。

10、Promise.resolve()

有时须要将现有对象转为 Promise 对象,Promise.resolve()方法就起到这个做用。

const jsPromise = Promise.resolve($.ajax('/whatever.json'));

Promise.resolve方法的参数分红四种状况。

(1)参数是一个 Promise 实例

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

(2)参数是一个thenable对象

thenable对象指的是具备then方法的对象,Promise.resolve方法会将这个对象转为 Promise 对象,而后就当即执行thenable对象的then方法。

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

若是参数是一个原始值,或者是一个不具备then方法的对象,则Promise.resolve方法返回一个新的 Promise 对象,状态为resolved

(4)不带有任何参数

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

因此,若是但愿获得一个 Promise 对象,比较方便的方法就是直接调用Promise.resolve()方法。

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

上面代码中,setTimeout(fn, 0)在下一轮“事件循环”开始时执行,Promise.resolve()在本轮“事件循环”结束时执行,console.log('one')则是当即执行,所以最早输出。

11、Promise.reject() 

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

注意:Promise.reject()方法的参数,会原封不动地做为reject的理由,变成后续方法的参数。这一点与Promise.resolve方法不一致。

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

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

上面代码中,Promise.reject方法的参数是一个thenable对象,执行之后,后面catch方法的参数不是reject抛出的“出错了”这个字符串,而是thenable对象。

12、应用

加载图片

咱们能够将图片的加载写成一个Promise,一旦加载完成,Promise的状态就发生变化。

const preloadImage = function (path) {
  return new Promise(function (resolve, reject) {
    const image = new Image();
    image.onload  = resolve;
    image.onerror = reject;
    image.src = path;
  });
};

Generator 函数与 Promise 的结合

使用 Generator 函数管理流程,遇到异步操做的时候,一般返回一个Promise对象。

function getFoo () {
  return new Promise(function (resolve, reject){
    resolve('foo');
  });
}

const g = function* () {
  try {
    const foo = yield getFoo();
    console.log(foo);
  } catch (e) {
    console.log(e);
  }
};

function run (generator) {
  const it = generator();

  function go(result) {
    if (result.done) return result.value;

    return result.value.then(function (value) {
      return go(it.next(value));
    }, function (error) {
      return go(it.throw(error));
    });
  }

  go(it.next());
}

run(g);

上面代码的 Generator 函数g之中,有一个异步操做getFoo,它返回的就是一个Promise对象。函数run用来处理这个Promise对象,并调用下一个next方法。

十3、Promise.try()

实际开发中,常常遇到一种状况:不知道或者不想区分,函数f是同步函数仍是异步操做,可是想用 Promise 来处理它。由于这样就能够无论f是否包含异步操做,都用then方法指定下一步流程,用catch方法处理f抛出的错误。通常就会采用下面的写法。

Promise.try(() => database.users.get({id: userId}))
  .then(...)
  .catch(...)

十4、手写promise

这里参考了http://www.javashuo.com/article/p-tibvniwq-ey.html

//Promise/A+规定的三种状态
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

class MyPromise {
  // 构造方法接收一个回调
  constructor(executor) {
    this._status = PENDING     // Promise状态
    this._value = undefined    // 储存then回调return的值
    this._resolveQueue = []    // 成功队列, resolve时触发
    this._rejectQueue = []     // 失败队列, reject时触发

    // 因为resolve/reject是在executor内部被调用, 所以须要使用箭头函数固定this指向, 不然找不到this._resolveQueue
    let _resolve = (val) => {
      //把resolve执行回调的操做封装成一个函数,放进setTimeout里,以兼容executor是同步代码的状况
      const run = () => {
        if(this._status !== PENDING) return   // 对应规范中的"状态只能由pending到fulfilled或rejected"
        this._status = FULFILLED              // 变动状态
        this._value = val                     // 储存当前value

        // 这里之因此使用一个队列来储存回调,是为了实现规范要求的 "then 方法能够被同一个 promise 调用屡次"
        // 若是使用一个变量而非队列来储存回调,那么即便屡次p1.then()也只会执行一次回调
        while(this._resolveQueue.length) {    
          const callback = this._resolveQueue.shift()
          callback(val)
        }
      }
      setTimeout(run)
    }
    // 实现同resolve
    let _reject = (val) => {
      const run = () => {
        if(this._status !== PENDING) return   // 对应规范中的"状态只能由pending到fulfilled或rejected"
        this._status = REJECTED               // 变动状态
        this._value = val                     // 储存当前value
        while(this._rejectQueue.length) {
          const callback = this._rejectQueue.shift()
          callback(val)
        }
      }
      setTimeout(run)
    }
    // new Promise()时当即执行executor,并传入resolve和reject
    executor(_resolve, _reject)
  }

  // then方法,接收一个成功的回调和一个失败的回调
  then(resolveFn, rejectFn) {
    // 根据规范,若是then的参数不是function,则咱们须要忽略它, 让链式调用继续往下执行
    typeof resolveFn !== 'function' ? resolveFn = value => value : null
    typeof rejectFn !== 'function' ? rejectFn = reason => {
      throw new Error(reason instanceof Error? reason.message:reason);
    } : null
  
    // return一个新的promise
    return new MyPromise((resolve, reject) => {
      // 把resolveFn从新包装一下,再push进resolve执行队列,这是为了可以获取回调的返回值进行分类讨论
      const fulfilledFn = value => {
        try {
          // 执行第一个(当前的)Promise的成功回调,并获取返回值
          let x = resolveFn(value)
          // 分类讨论返回值,若是是Promise,那么等待Promise状态变动,不然直接resolve
          x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
        } catch (error) {
          reject(error)
        }
      }
  
      // reject同理
      const rejectedFn  = error => {
        try {
          let x = rejectFn(error)
          x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
        } catch (error) {
          reject(error)
        }
      }
  
      switch (this._status) {
        // 当状态为pending时,把then回调push进resolve/reject执行队列,等待执行
        case PENDING:
          this._resolveQueue.push(fulfilledFn)
          this._rejectQueue.push(rejectedFn)
          break;
        // 当状态已经变为resolve/reject时,直接执行then回调
        case FULFILLED:
          fulfilledFn(this._value)    // this._value是上一个then回调return的值(见完整版代码)
          break;
        case REJECTED:
          rejectedFn(this._value)
          break;
      }
    })
  }

  //catch方法其实就是执行一下then的第二个回调
  catch(rejectFn) {
    return this.then(undefined, rejectFn)
  }

  //finally方法
  finally(callback) {
    return this.then(
      value => MyPromise.resolve(callback()).then(() => value),             //执行回调,并returnvalue传递给后面的then
      reason => MyPromise.resolve(callback()).then(() => { throw reason })  //reject同理
    )
  }

  //静态的resolve方法
  static resolve(value) {
    if(value instanceof MyPromise) return value //根据规范, 若是参数是Promise实例, 直接return这个实例
    return new MyPromise(resolve => resolve(value))
  }

  //静态的reject方法
  static reject(reason) {
    return new MyPromise((resolve, reject) => reject(reason))
  }

  //静态的all方法
  static all(promiseArr) {
    let index = 0
    let result = []
    return new MyPromise((resolve, reject) => {
      promiseArr.forEach((p, i) => {
        //Promise.resolve(p)用于处理传入值不为Promise的状况
        MyPromise.resolve(p).then(
          val => {
            index++
            result[i] = val
            if(index === promiseArr.length) {
              resolve(result)
            }
          },
          err => {
            reject(err)
          }
        )
      })
    })
  }

  //静态的race方法
  static race(promiseArr) {
    return new MyPromise((resolve, reject) => {
      //同时执行Promise,若是有一个Promise的状态发生改变,就变动新MyPromise的状态
      for (let p of promiseArr) {
        MyPromise.resolve(p).then(  //Promise.resolve(p)用于处理传入值不为Promise的状况
          value => {
            resolve(value)        //注意这个resolve是上边new MyPromise的
          },
          err => {
            reject(err)
          }
        )
      }
    })
  }
}