# Promise

Promise 对象是一个构造函数,用来生成 Promise 实例

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

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

Promise 新建后就会立即执行传入的函数,并且已同步的方式执行

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

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

console.log('Hi!');

// Promise
// Hi!
// resolved

# resolve方法

当调用 resolve() 方法, Promise 的状态将从 pending 置为 fulfilledresolve() 可传递参数,该参数会被 Promise 实例的 then 方法的每个参数接收。

注意当 resolve() 传递的参数是一新的个 Promise 时,那么原来的 Promise 状态将会被这个新的 Promise 参数的影响

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

# reject方法

当调用 resolve() 方法, Promise 的状态将从 pending 置为 rejected

# Promise.prototype.then()

then() 的作用是为 Promise 实例添加状态改变时的回调函数。 then 方法的第一个参数是 resolved 状态的回调函数,第二个参数(可选)是 rejected 状态的回调函数

then 方法返回的是一个新的 Promise 实例(注意,不是原来那个 Promise 实例)。因此可以采用链式写法,即 then 方法后面可以再调用 then 方法

function fetch(time){
    return new Promise((resolve, reject) => {
      setTimeout(resolve, time)
    })
  }
const p1 = new Promise(function (resolve, reject) {
  fetch(2000, true)
  .then(res => {
    resolve('data1')
  })
})
p1.then(res => {
  console.log(res) // 2秒后 打印 data1
  return fetch(1000)
})
.then(res => {
  console.log(res) // 再过1秒后 打印 undefind
  return 'end'
})
.then(res => {
    console.log(res) // 基本跟上一次打印同时输出 'end'
})

# Promise.prototype.catch()

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

p.then((val) => console.log('fulfilled:', val))
  .catch((err) => console.log('rejected', err));

// 等同于
p.then((val) => console.log('fulfilled:', val))
  .then(null, (err) => console.log("rejected:", err));

catch 不仅可以接收 Promise 内部执行 reject 方法触发的错误,也可以接收 then 方法中错误

Promise.resolve('执行成功')
.then(res => {
  console.log(res) // 执行成功
  throw Error("then 错误")
})
.then(res => {
  console.log(res) 
  return 'end'
})
.catch(err => {
    console.log('err1:', err)  // err1: Error then 错误
    return "catch 继续走"
})
.then(res => {
  console.log('then', res) // thenr catch 继续走
  throw Error("then 错误2")
})
.catch(err => {
    console.log('err2:', err)  // err1: Error then 错误
    return Promise.reject('catch2')
})
.catch(err => {
    console.log('err3:', err)  // catch2
})

从上面的例子可以看到几点规律:

  • catch 方法也可以支持链式调用

  • 无论是在 Promise 中还是在 then , catch 中,抛出的错误总能被后面 catch 捕获

  • catchreturn 的值会被后面的 then 方法继续接收

如果 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

如果没有使用 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

# Promise.prototype.finally()

finally() 方法用于指定不管 Promise 对象最后状态如何,都会执行的操作, 并且返回一个设置了 finally 回调函数的 Promise 对象

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

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

Promise.resolve('执行成功')
.then(res => {
  console.log(res) // 执行成功
  return 'then'
})
.finally(() => {
  console.log('finally') // finally
  return 'F'
})
.then(res => {
  console.log(res') // then
})
  • finally 后面可以链式调用 then 方法收接前面的 resolve 任务,当然也可以使用 catch 方法收接前面的 reject 错误

finally() 虽然与 .then(onFinally, onFinally) 类似,它们不同的是:

  • 由于无法知道 Promise 的最终状态,所以 finally 的回调函数中不接收任何参数,它仅用于无论最终结果如何都要执行的情况

  • Promise.resolve(2).then(() => {}, () => {})resolved 的结果为 undefined )不同,Promise.resolve(2).finally(() => {}) resolved 的结果为 2

  • 同样,Promise.reject(3).then(() => {}, () => {}) ( resolved 的结果为 undefined ), Promise.reject(3).finally(() => {}) rejected 的结果为 3

# Promise.resolve()

Promise.resolve() 等价于下面的写法

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

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

参数是一个 Promise 实例

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

参数是一个 thenable 对象

thenable 对象指的是具有 then 方法的对象,比如下面这个对象

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

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

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

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

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

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

不带有任何参数

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

# 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.all()

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

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

上面例子中 p 的状态由 p1p2p3 决定

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

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

Promise.all([Promise.resolve(), Promise.reject('err')])
.then(res => {
  console.log(res)
})
.catch(err => {
  console.log(err) // err
})

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

const catchPro = Promise.reject('err')
  .catch(err => console.log(err))

Promise.all([Promise.resolve('res'), catchPro])
  .then(res => {
    console.log(res) // ["res", undefined]
  })
  .catch(err => {
    console.log(err)
  })

因为 catchPro 定义了 catch, 所以不会触发 Promise.all()catch 方法, 因为 catchPro.catch 返回也是一个 Promise , 所以上面 Promise.all 每个 Promise 实例实际上是 catchPro.catch 返回的 Promise

const catchPro = Promise.reject('err')
  .catch(err => {
    throw Error('err')
  })
Promise.all([Promise.resolve('res'), catchPro])
  .then(res => {
    console.log(res)
  })
  .catch(err => {
    console.log('err', err) // err Error: err
  })

如果 catch 内抛出错误且当前 Promise 实例后面没有 catch 接收,这时才会被 Promise.allcatch 接收

# Promise.race()

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

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

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

function fetch(time){
  return new Promise((resolve, reject) => {
    setTimeout(resolve(10), time)
  })
}
const catchPro = Promise.reject('err')
Promise.race([catchPro, fetch(1000)])
  .then(res => {
    console.log(res)
  })
  .catch(err => {
    console.log('err', err) // err, err
  })

下面是一个例子,如果指定时间内没有获得结果,就将 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);

上面代码中,如果 5 秒之内 fetch 方法无法返回结果,变量p的状态就会变为 rejected ,从而触发 catch 方法指定的回调函数

# Promise.allSettled()

Promise.allSettled() 方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只有等到所有这些参数实例都返回结果,不管是 fulfilled还是 rejected ,包装实例才会结束。该方法由 ES2020 引入

Promise.allSettled([fetch(2000, false), fetch(1000)])
  .then(res => {
    console.log(res)
  })
  .catch(err => {
    console.log('err', err)
  })

上面的代码将在 3 秒后输出结果:

0: {status: "rejected", reason: 10}
1: {status: "fulfilled", value: 10}

返回数组对象都有 status 属性,该属性的值只可能是字符串 fulfilled 或字符串 rejected

  • fulfilled 时,对象有 value 属性

  • rejected 时,对象有 reason 属性

该方法返回的新的 Promise 实例,一旦结束,状态总是 fulfilled,不会变成 rejected

# Promise.any()

Promise.any() 方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只要参数实例有一个变成 fulfilled 状态,包装实例就会变成 fulfilled 状态;如果所有参数实例都变成 rejected 状态,包装实例就会变成 rejected 状态。该方法目前是一个第三阶段的提案

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

function fetch(time, sue = true){
  return new Promise((resolve, reject) => {
    let callback = sue ? resolve : reject
    setTimeout(() => {
      callback(10)
    }, time)
  })
}
// 待验证,浏览器目前不支持此方法
Promise.any([fetch(1000, false), fetch(1000)])
  .then(res => {
    console.log(res)
  })
  .catch(err => {
    console.log('err', err) // err, err
  })

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

// 待验证

# Promise.try()

// todo

# 应用

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 方法。

# Promise实现

下面是 Promise 的简单实现

class myPromise{
    constructor(handle) {
      this.status = 'PENDING'
      try{
        handle(this.resolve.bind(this), this.reject.bind(this))
      } catch (e){
        this.reject(e)
      }
      this.doResolve = null
      this.doReject = null
    }
    resolve(val){
      if(this.status !== 'PENDING') return
      this.status === 'FULFILLED'
      queueMicrotask(() => {
        this.doResolve(val)
      })
    }
    reject(val){
      if(this.status !== 'PENDING') return
      this.status === 'REJECTED'
       queueMicrotask(() => {
        this.doReject(val)
      })
    }
    then(fn) {
      this.doResolve = fn
    }
    catch(fn) {
      this.doReject = fn
    }
  }
  var pro = new myPromise((resolve, reject) => {
    setTimeout(() => {resolve('执行成功')}, 2000)
  })
  pro.then(res => {console.log(res)})
  • 使用 queueMicrotaskthen 的执行加入到微任务队列中

  • handle(this.resolve.bind(this), this.reject.bind(this)),传递自己的 resolvereject 实例给外面的使用

# 支持多个then

  class myPromise{
    constructor(handle) {
      this.status = 'PENDING'
      try{
        handle(this.resolve.bind(this), this.reject.bind(this))
      } catch (e){
        this.reject(e)
      }
      this.doResolve = []
      this.doReject = null
    }
    resolve(val){
      if(this.status !== 'PENDING') return
      this.status === 'FULFILLED'
      queueMicrotask(() => {
        this.doResolve.forEach(itemFn => {
          itemFn(val)
        })
      })
    }
    reject(val){
      if(this.status !== 'PENDING') return
      this.status === 'REJECTED'
       queueMicrotask(() => {
        this.doReject(val)
      })
    }
    then(fn) {
      this.doResolve.push(fn)
    }
    catch(fn) {
      this.doReject = fn
    }
  }
  var pro = new myPromise((resolve, reject) => {
    setTimeout(() => {resolve('执行成功')}, 2000)
  })
  pro.then(res => {console.log(res)})
  pro.then(res => {console.log('then2'+res)})

# then返回promise

 class myPromise{
    constructor(handle) {
      this.status = 'PENDING'
      try{
        handle(this.resolve.bind(this), this.reject.bind(this))
      } catch (e){
        this.reject(e)
      }
      this.doResolve = []
      this.doReject = null
      this.sonResovle = null
      this.sonReject = null
    }
    resolve(val){
      if(this.status !== 'PENDING') return
      this.status === 'FULFILLED'
      queueMicrotask(() => {
        this.doResolve.forEach(itemFn => {
          itemFn(val)
          this.sonResovle(val)
        })
      })
      
    }
    reject(val){
      if(this.status !== 'PENDING') return
      this.status === 'REJECTED'
      queueMicrotask(() => {
       this.doReject(val)
      })
      
    }
    then(fn) {
      this.doResolve.push(fn)
      const _this = this
      return new myPromise((resolve, reject) => {
        _this.sonResovle = resolve
        _this.sonReject = reject
      })
    }
    catch(fn) {
      this.doReject = fn
    }
  }
  var pro = new myPromise((resolve, reject) => {
    setTimeout(() => {resolve('执行成功')}, 2000)
  })
  pro.then(res => {console.log(res)})
  pro.then(res => {console.log('then2'+res)})
    .then(res => console.log('sonThen' + res))
    .then(res => console.log('sonThen2' + res))