1+1=10

记记笔记,放松一下...

JavaScript学习笔记(六)

接前面的:

继续学习JavaScript。

Promise

Promise是现代JavaScript中异步编程的基础。

Promise应该翻译成 承诺?承诺会提供一个值,如果不能提供值会提供错误原因?

概念

Promise是由异步函数返回的一个对象,它表示操作的当前状态。在将Promise返回给调用者的时候,操作通常尚未完成,但是Promise对象提供了方法来响应其最终成功或失败的场景。

Promise 有三种状态:

  • pending:待定状态,初始状态。
  • fulfilled:已实现状态,表示操作已成功完成。
  • rejected:已拒绝状态,表示操作失败。

处于pending状态的Promise最终状态可以是以某个值 进入fulfilled状态或以某个原因(error)进入 rejected状态。当其中任何一种情况发生时,由Promise的then方法排队的相关处理程序将被调用。如果在附加相应处理程序时Promise已经完成或拒绝,处理程序将被立即调用。

fulfilled或rejected状态都可称为settled(已完成)状态。

javascript-promise

构造

Promise只能使用new来构造:

1
new Promise(executor)

Promise构造函数接受一个执行器函数,这个函数长这样:

1
2
3
4
5
function executor(resolveFunc, rejectFunc) {
  // 异步操作,例如从服务器获取数据
  // 如果成功,调用 resolveFunc,传递结果
  // 如果失败,调用 rejectFunc,传递错误信息
}

注:执行器函数是在promise创建时,同步执行的!执行器函数的返回值没有用。

它有两个参数 resolveFuncrejectFunc,分别用于将 Promise 状态从 Pending 改为 Fulfilled 或 Rejected。

  • resolveFunc(value):将 Promise 的状态从 Pending 转变为 Fulfilled(已实现),并携带一个指定的解决值 value。这表示异步操作成功完成。
  • rejectFunc(reason):将 Promise 的状态从 Pending 转变为 Rejected(已拒绝),并携带一个指定的拒绝原因 reason。这表示异步操作遇到了错误或失败。

由于这两个函数是Promise构造时自动生成的,要想在执行器函数的外边执行它,很不方便,需要通过变量先传递出去:

1
2
3
4
5
let resolve, reject;
const promise = new Promise((res, rej) => {
  resolve = res;
  reject = rej;
});

为了减轻这个工作,ES2023为Promise引入了静态函数withResolvers,它可以同时返回创建的promise对象以及对应的resolveFuncrejectFunc

1
{ promise, resolveFunc, rejectFunc } = Promise.withResolvers()

不过Node.js尚不支持它。

作为例子,对于Promise创建,可以写一个返回promise的函数,让执行器函数使用setTimeout模拟异步操作:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// 创建一个返回 Promise 的异步函数
function asyncOperation(duration) {
    return new Promise((resolveFunc, rejectFunc) => {
      // 模拟异步操作,使用WebAPI/Nodejs的 setTimeout()
      setTimeout(() => {
        const randomNumber = Math.random();
        if (randomNumber > 0.5) {
          // 模拟异步操作成功
          resolveFunc(`Operation succeeded: ${randomNumber}`);
        } else {
          // 模拟异步操作失败
          rejectFunc(new Error(`Operation failed: ${randomNumber}`));
        }
      }, duration);
    });
  }

链式操作

Promise通过then()、catch()、finally() 实现链式操作,它们三个都会返回一个新的promise对象:

  • promise.prototype.then()
  • promise.prototype.catch()
  • promise.prototype.finally()

其参数:

1
2
3
4
5
6
then(onFulfilled)
then(onFulfilled, onRejected)

catch(onRejected)

finally(onFinally)

其中,涉及的三个回调函数,长这个样子:

1
2
3
onFulfilled(value)
onRejected(error)
onFinally()

then()catch()中:如果onFulfilled不是函数的话,内部它会被替换成(x)=>x;如果onRejected不是函数的话,内部会被替换成(x) => { throw x; }

注:

  • catch(onRejected)then(null, onRejected)的简写形式;而
  • finally(onFinally)then(onFinally, onFinally)行为有些像,不管怎样回调都会执行。但不用于then,finally中的回调函数没有参数。

所以,先看then() 好了,看看它返回的新的 Promise 是怎么来的?

不管执行then()中哪一个回调函数:

回调函数 新的 promise 备注
无返回值 fulfilled状态,携带值是undefined? -
返回一个值 fulfilled状态,使用这个返回值 -
返回promise对象 与该promise的状态和值保持一致 -
抛出异常 rejected状态,携带抛出的异常 -

在回调函数中,我们可以抛出异常,没有返回值,返回一个值,或者返回另一个promise对象。不管怎样,then()、catch() 都会基于其结果返回一个新的promise对象。不用我们传入执行器函数来手动创建promise对象。

对于finally(onFinally)返回的promise来说,onFinally的返回值被默认被忽略(除非其返回值是一个rejected状态的promise)。

接前面构造的例子,继续通过例子看看then()的使用:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
  // 使用 Promise 进行异步操作处理
  asyncOperation(2000)
    .then((result) => {
      // 在异步操作成功时执行的回调函数
      console.log(result);
      return 'Additional data';  // 返回一个值,将传递给下一个 then
    })
    .then((additionalData) => {
      // 在上一个 then 方法成功回调函数返回值的基础上执行
      console.log(`Received additional data: ${additionalData}`);
    })
    .catch((error) => {
      // 捕获异步操作失败时的错误
      console.error(error.message);
    })
    .finally(() => {
      // 不论 Promise 最终状态如何,都会执行的回调函数
      console.log('Finally block executed');
    });

运行结果:

1
2
3
Operation succeeded: 0.6106660469894274
Received additional data: Additional data
Finally block executed

运行结果:

1
2
Operation failed: 0.03309163424930217
Finally block executed

其他成员

Promise还有其他一些成员,都会返回一个新的promise对象:

  • Promise.all()
  • Promise.any()
  • Promise.race()
  • Promise.reject()
  • Promise.resolve()

前三个都接收一个可迭代的对象(通常是包含多个 Promise 的数组),返回一个新的 Promise。后两个可以看作是用于创建指定状态的Promise,具体看后面例子。

reject 始终会创建新的promise,resolve不一定,而且resolve还可以接受thenable

例子

抛开文字,通过简单的例子找找感觉:

then() 返回的promise

回调有返回值,p为fulfilled状态,且使用该返回值:

1
2
3
4
5
6
7
8
9
let p = Promise.resolve('1+1=10').then((value) => {
  return value.replace('10', '2');
});

p.then((value) => {
  console.log(value);
});

// output: 1+1=2

回调无返回值,p为fulfilled状态,且使用undefined:

1
2
3
4
5
6
7
8
9
let p = Promise.resolve('1+1=10').then((value) => {

});

p.then((value) => {
  console.log(value);
});

// output: undefined

回调返回promise,p状态与值与回调返回的promise一样:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
let p = Promise.resolve('1+1=10').then((value) => {
    if (Math.random() > 0.5)
        return Promise.resolve('1+1=2');
    return Promise.reject('1+1=3');
});

p.then((value) => {
  console.log(`resolved: ${value}`);
}).catch((error) => {
  console.log(`rejected: ${error}`);
});

// output: resolved: 1+1=2 or rejected: 1+1=3

回调抛出异常,p状态为rejected:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
let p = Promise.resolve('1+1=10').then((value) => {
    throw new Error('1+1=3');
});

p.then((value) => {
  console.log(`resolved: ${value}`);
}).catch((error) => {
  console.log(`rejected: ${error}`);
});

// output: rejected: Error: 1+1=3

刚才用了then的onFulfulled,使用onRejected时,结论与前面一样:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
let p = Promise.reject('1+1=10').then(null, (value) => {
    return "1+1=10";
});

p.then((value) => {
  console.log(`resolved: ${value}`);
}).catch((error) => {
  console.log(`rejected: ${error}`);
});

// output: resolved: 1+1=10

catch() 返回的promise

与then其实一样

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
let p = Promise.reject('1+1=10').catch((value) => {
    return "1+1=10";
});

p.then((value) => {
  console.log(`resolved: ${value}`);
}).catch((error) => {
  console.log(`rejected: ${error}`);
});

// output: resolved: 1+1=10

finally() 返回的promise

不受回调返回值影响,与原promise的状态和值都一样

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
let p = Promise.reject('1+1=10').finally(() => {

})

p.then((value) => {
  console.log(`resolved: ${value}`);
}).catch((error) => {
  console.log(`rejected: ${error}`);
});

// output: resolved: 1+1=10

回调返回fulfilled的promise,对p没影响

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
let p = Promise.reject('1+1=10').finally(() => {
    return Promise.resolve('1+1=2');
})

p.then((value) => {
  console.log(`resolved: ${value}`);
}).catch((error) => {
  console.log(`rejected: ${error}`);
});

// output: resolved: 1+1=10

回调返回rejected的promise,对p有影响!!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
let p = Promise.reject('1+1=10').finally(() => {
    return Promise.reject('1+1=3');
})

p.then((value) => {
  console.log(`resolved: ${value}`);
}).catch((error) => {
  console.log(`rejected: ${error}`);
});

// output: rejected: 1+1=3

回调抛出异常,和then预期一样

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
let p = Promise.resolve('1+1=10').finally(() => {
    throw new Error('1+1=3');
})

p.then((value) => {
  console.log(`resolved: ${value}`);
}).catch((error) => {
  console.log(`rejected: ${error}`);
});

// output: rejected:Error: 1+1=3

Promise.reject()生成的promise

Promise.reject()等价于

1
new Promise((resolveFunc, rejectFunc)=> rejectFunc(reason))

所以:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Promise.reject('1+1=3').catch((reason) => {
    console.log('rejected:', reason);
});

new Promise((resolveFunc, rejectFunc)=> rejectFunc('1+1=3')).catch((reason) => {
    console.log('rejected:', reason);
});

// rejected: 1+1=3
// rejected: 1+1=3

Reomise.resolve()返回的promise

这个静态函数用于将给定值变成一个promise!

1
2
3
4
5
6
7
let p = Promise.resolve('1+1=10');

p.then((v) => {
  console.log(v);
});

//output: 1+1=10    

如果给他一个promise,则会直接返回

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
let p = Promise.resolve(Promise.reject('1+1=3'));

p.then((value) => {
    console.log(`resolved: ${value}`);
  }).catch((error) => {
    console.log(`rejected: ${error}`);
  });

// Output:
// rejected: 1+1=3

额,应该这样

1
2
3
4
let p0 = Promise.reject('1+1=3');
let p = Promise.resolve(p0);

console.log(p === p0); // true

resolve()还接收thenable。thenable需要实现接收两个回调函数的then()方法,早期Promise标准化之前有各种promise实现都实现了这个theable协议。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
const aThenable = {
    then(onFulfilled, onRejected) {
        onFulfilled('1+1=10');
    }
}

Promise.resolve(aThenable).then((result) => {
    console.log(result);
});

// Output: 1+1=10

这个神奇的函数,还能接收和Promise的构造函数长的一样的非promise构造函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class NotPromise {
    constructor(executor) {
      executor(
        (value) => console.log("Resolved", value),
        (reason) => console.log("Rejected", reason),
      );
    }
  }

Promise.resolve.call(NotPromise, "1+1=10"); // Resolved 1+1=10

参考

  • https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
  • https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises
  • https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise