One, foreword

The traditional solution to single-threaded code execution is callback functions and events. This is a solution to the problem, but it creates callback hell.

Asynchronous programming is the key to optimizing code logic and improving code readability.

There are three common asynchronous programming methods:

  1. Promise
  2. generator+co
  3. async+await

I use all three methods frequently, but I don’t really understand how they work. So I want to fry a cold rice from beginning to end to sort out the relationship between them.

Second, the Promise

2.1 the principle

The Promise object is a constructor that generates a Promise instance.

The Promise object represents an asynchronous operation with three states: Pending, fulfilled and Rejected.

The two arguments to the Promise function are resolve and reject. These are the two functions defined in Promise that are returned when the custom function is run.

The resolve function changes the state of the Promise object from Pending to Resolved, and the Reject function changes the state of the Promise object from Pending to Rejected

Promise’s prototype chain defines the then method, which provides two callback functions to capture the value returned by resolve and Reject.

2.2 Static Method
methods describe
Promise.resolve(promise); Return a promise (only if promise.constructor == promise)
Promise.resolve(thenable); Generate a new promise from thenable. Thenable isthen()Method similar to the promise object.
Promise.resolve(obj); In this case, a promise is generated and obj is returned on execution.
Promise.reject(obj); Generate a Promise and return obj on rejection. For consistency and debugging purposes (such as stack tracing), obj should be instanceof Error.
Promise.all(array); Generates a promise that is executed when each item in the array is executed and rejected when any item is rejected.
Promise.race(array); Generates a Promise that is executed when any item is executed or rejected when any item is rejected, whichever occurs first.

sample 1

let p1 = new Promise((resolve,reject)=>{
    console.log('hello')
    setTimeout(function () {
        reject('1212')
    },1000)
})

p1.then(data=> {
    console.log('success'+data)
},err=>{
    console.log('err'+err)
})

p1.then(data=> {
    console.log('success'+data)
},err=>{
    console.log('err'+err)
})
Copy the code

terminal:

hello
err1212
err1212
Copy the code

A new Promise instance is created in Sample 1. After 1 seconds of timing, reject is used to change the state of the Promise instance from Pending to Rejected, and the ERR-capturing callback function of THEN is triggered.

Calling the THEN method in Sample 1 does not execute the callback immediately. Wait for state changes in the instance before executing. This is similar to the publish-subscribe model.

sample 2

let fs = require('fs')

let event = {
    arr:[],
    result:[],
    on(fn){
        this.arr.push(fn)
    },
    emit(data){
        this.result.push(data)
        this.arr.forEach(fn=>fn(this.result))
    }
}

event.on(function (data) {
    if(data.length === 2){
        console.log(data)
    }
})

fs.readFile('1.txt','utf8',function (err,data) {
    event.emit(data)
})
fs.readFile('2.txt','utf8',function (err,data) {
    event.emit(data)
})
Copy the code

Smaple2 puts the result data into a staging array and returns it when the call function is executed.

2.3 Abbreviate Promise source code

With our previous examples and our understanding of the publish-subscribe model, we can outline the basic functionality of a Promise instance:

Code 1:

function Promise(executor) { let self = this self.value = undefined self.reason = undefined self.status = 'pending' self.onResovedCallbacks = [] self.onRejectedCallbacks = [] function resolve(data) { if(self.status === 'pending'){ self.value = data self.status = 'resolved' self.onResovedCallbacks.forEach(fn=>fn()) } } function reject(reason) { if(self.status === 'pending') { self.reason = reason self.status = 'reject' self.onRejectedCallbacks.forEach(fn=>fn()) } } // Try {executor(resolve,reject)}catch (e){reject(e)}} promise.prototype. Then = function (onFulfilled,onRejected) { let self = this if(self.status === 'pending'){ self.onResovedCallbacks.push(()=>{ onFulfilled(self.value) }) self.onRejectedCallbacks.push(()=>{ onRejected(self.reason) }) }else if(self.status === 'resolved'){ onFulfilled(self.value) }else if(self.status === 'reject'){ onRejected(self.reason) } } module.exports = PromiseCopy the code
  • Function internal variable
    • Status: Stores the status of the Promise
    • OnResovedCallbacks: Stores the success callback function in the Promise pending state
    • OnRejectedCallbacks: Stores failed callback functions in the Promise pending state
    • Resolve function
    • Reject function
  • Promise.prototype.then
    • The callback of the response is performed based on the instance state
    • Status == pending stores callback functions in publish-subscribe mode.
2.4 Brief Usage description of Promise
  1. If a promise is executed and a promise object is returned, the execution result of that promise is passed to the next THEN.
let fs = require('fs')

function read(filePath,encoding) {
    return new Promise((resolve,reject)=>{
        fs.readFile(filePath,encoding,(err,data)=> {
            if(err) reject(err)
            resolve(data)
        })
    })
}

read('1.txt','utf8').then(
    f1=>read(f1,'utf8') // 1
).then(
    data=> console.log('resolved:',comments)
    err=> console.log('rejected: ',err)
)
Copy the code
  1. If a normal value is returned instead of a promise, that normal value will be returned as the result of the next THEN.
. read('1.txt','utf8').then( f1=>read(f1,'utf8') ).then( return 123 //2 ).then( data=> console.log('resolved:',comments) err=> console.log('rejected: ',err) )Copy the code
  1. If the current THEN fails, the next THEN fails.
. Read ('1.txt','utf8').then(f1=>read(f1,'utf8').then(return 123).then(throw new Error(' Error ') //3).then(data=> console.log('resolved:',comments) err=> console.log('rejected: ',err) )Copy the code
  1. If undefined is returned, no matter whether the current is a failure or a success, the next success will go.
  2. A catch is an error that goes without being handled.
  3. You don’t have to write “then”.
. 1. TXT read (' ', 'utf8). Then (f1 = > read (f1,' utf8)), then (return 123). Then (throw new Error (' Error ')). Then () / / 6. Then, data=> console.log('resolved:',comments) err=> console.log('rejected: ',err) )Copy the code

The most important of these uses is the then chain invocation of promise. As you can roughly guess, the then method of the old Promise returns a new Promise object.

Promises/A+ specification to perfect the handwritten Promise source code to support Promise’s static methods and invocation rules.

Code 2:

function Promise(executor) { let self = this self.value = undefined self.reason = undefined self.status = 'pending' self.onResovedCallbacks = [] self.onRejectedCallbacks = [] function resolve(value) { if (self.status === 'pending') { self.value = value self.status = 'resolved' self.onResovedCallbacks.forEach(fn=>fn()) } } function reject(reason) { if (self.status === 'pending') { self.reason = reason self.status = 'rejected' self.onRejectedCallbacks.forEach(fn=>fn()) } } // if there is an exception, try {executor(resolve, reject)} catch (e) {reject(e)}} function resolvePromise(promise2, x, resolve, reject) { //If promise and x refer to the same object, reject promise with a TypeError as the reason. if (promise2 === x) { return reject(new TypeError('chaining cycle')) } Let called //2.3.3.Otherwise, if x is an object or function, if (x! = = = = null && (typeof x 'object' | | typeof x = = = 'function')) {try {let then = x.t hen / / 2.3.3.3. If then is a function,  call it with x as this, first argument resolvePromise, and second argument rejectPromise, where: If both resolvePromise and rejectPromise are called, or multiple calls to the same argument are made, the first call takes precedence, and any further calls are ignored. if (typeof then === 'function') { then.call(x, y=> { if (called) return; called = true; If/when resolvePromise is called with a value y, run [[Resolve]](resolvePromise, y). resolvePromise(promise2, y, resolve, reject) }, err=> { if (called) return; called = true; reject(err) }) } else { resolve(x) } } catch (e) { if (called) return; called = true; > > Thrown exception > thrown thrown for retrieving the property. > > thrown exception > > thrown for retrieving the property. reject promise with e as the reason. reject(e) } } else { //If x is not an object or function, Fulfill promise with x. resolve(x)} // fulfill promise with x. resolve(x)} // fulfill promise with x. resolve(x)} // Fulfill promise with x (onFulfilled, onFulfilled) {// This is a big pity, onFulfilled) {// This is a big pity, onFulfilled === = 'function'? onFulfilled : val=>val; onRejected = typeof onRejected === 'function' ? onRejected : (e)=> {throw e}; let self = this let promise2; promise2 = new Promise((resolve, reject)=> { if (self.status === 'resolved') { setTimeout(()=> { try { let x = onFulfilled(self.value) resolvePromise(promise2, x, resolve, reject) } catch (e) { reject(e) } }, 0) } else if (self.status === 'rejected') { setTimeout(()=> { try { let x = onRejected(self.reason) resolvePromise(promise2, x, resolve, reject) } catch (e) { reject(e) } }, 0) } else if (self.status === 'pending') { self.onResovedCallbacks.push(()=> { setTimeout(()=> { try { let x = onFulfilled(self.value) resolvePromise(promise2, x, resolve, Reject)} catch (e) {// When executing a successful callback, use this exception as a reject(e)}}, 0) }) self.onRejectedCallbacks.push(()=> { setTimeout(()=> { try { let x = onRejected(self.reason) resolvePromise(promise2, x, resolve, reject) } catch (e) { reject(e) } }, 1)})}) return promise. reject = function (reason) {return new Promise((resolve,reject)=>{ reject(reason) }) } Promise.resolve = function (value) { return new Promise((resolve,reject)=>{ resolve(value) }) } Promise.prototype.catch = function (onReject) { return this.then(null,onReject) } Promise.defer = Promise.deferred = function () { let dfd = {} dfd.promise = new Promise((resolve, reject)=> { dfd.resolve = resolve dfd.reject = reject }) return dfd; } module.exports = PromiseCopy the code
  1. To support chained calls to THEN, a new Promise object is returned in promise.then. Prototype
return p2 = new Promise()
Copy the code

2. Add resolvePromise method to handle the result x of the old Promise callback function, and call the resolve/ Reject methods of the new Promise object according to the type x.

  • Is a normal value returned by the resolve method
  • If it is a function or object, continue iterating with the resolvePromise method (resolve that the callback is a Promise object)
  • If there is an error, return with reject

Third, bluebird

1: Basic usage of the fs.readFile method in NodeJS

const fs = require('fs'),path = require('path'); fs.readFile(path.join(__dirname, '1.txt'), 'utf-8', (err, data) => { if (err) { console.error(err); } else { console.log(data); }});Copy the code

2: Use Promise encapsulation

let fs = require('fs')

function read(filePath, encoding) {
    return new Promise((resolve, reject)=> {
        fs.readFile(filePath, encoding, (err, data)=> {
            if (err) reject(err)
            resolve(data)
        })
    })
}

read('1.txt', 'utf8').then( data=> data)
Copy the code

Use the Promise API by wrapping the fs.readfile method with a Promise. But each manual packaging is troublesome, bluebird can help us simplify this step.

3: In a NodeJS environment, start using bluebird supplied Promise objects by const bluebird = require(‘bluebird’).

Promise.promisify converts a single method into a Promise object.

const bluebird = require('bluebird') 
let read = bluebird.promisify(fs.readFile)
read('1.txt', 'utf-8').then(data=> {
    console.log('data promisify', data)
})
Copy the code

Fs. readFile is encapsulated as a promise object using bluebird.promisify. Return New Promise:

function promisify(fn) { return function () { return new Promise((resolve, reject)=> { fn(... arguments, function (err, data) { if (err) reject(err) resolve(data) }) }) } }Copy the code

4. Use promises.promisifyAll Automatically converts all methods of an object to use promises.

const bluebird = require('bluebird'),
  fs = require('fs'),
  path = require('path');
Promise.promisifyAll(fs);
 
fs.readFileAsync(path.join(__dirname, 'sample.txt'), 'utf-8')
  .then(data => console.log(data))
  .catch(err => console.error(err));
Copy the code

The core of promisifyAll is to iterate over objects, generate the names of newly created methods and add the suffix “Async” to the names of existing methods.

function promisifyAll(obj) {
    Object.keys(obj).forEach(key=>{
        if(typeof obj[key] === 'function'){
            obj[key+'Async'] = promisify(obj[key])
        }
    })
}
Copy the code

Fourth, the generator + co

4.1 introduction

The most important feature of generator functions is that they can be paused with yield and are preceded by an asterisk (*) to distinguish themselves from ordinary functions.

function *say() {
    let a = yield "test1"
    let b = yield "test2"
}

let it = say();

console.log(1, it.next()) //1 { value: 'test1', done: false }
console.log(2, it.next()) //2 { value: 'test2', done: false }
console.log(3, it.next()) //3 { value: undefined, done: true }
Copy the code

The say() method returns a pointer object and does not return the result of the function execution. It is an iterator

You need to call the next() method of the pointer object to keep the function pointer moving and return an object. ({} value: XXX, done: XXX)

Value is the value after yield, and done indicates whether the function is complete.

We can use a generator function to produce the result, but we also need it to support the input.

The generator functions run in the following order:

The function is executed with it.next(), and the result is not returned to the defined variable A. The next method accepts parameters, which are input to the body of the Generator function. The second next is passed in as an argument, which is received by variable A.

Terminal return:

1 { value: 'test1', done: false }
aaa
2 { value: 'test2', done: false }
bbb
3 { value: undefined, done: true }
Copy the code

4.2 the use of

Example: Execute a function asynchronously using generator so that the return of the function is executed as an input parameter to the next function.

let bluebird = require('bluebird') let fs = require('fs') let read = bluebird.promisify(fs.readFile) function *r() { let  r1 = yield read('1.txt', 'utf-8') console.log('r1',r1); // r1 2.txt let r2 = yield read(r1, 'utf-8') console.log('r2',r2); // r2 3.txt let r3 = yield read(r2, 'utf-8') console.log('r3',r3); // r3 hello return r3 }Copy the code

Take the example of reading a file: use bluebird to turn fs.readFile into a promise object, passing the contents of the readFile as an input parameter to the next function to be executed.

Suddenly I realized that it would be a complicated process to get the results, but I went ahead and did it:

const it_r = r()
it_r.next().value.then(d1=>{
    return it_r.next(d1).value
}).then(d2=>{
    return it_r.next(d2).value
}).then(d3=>{
    return it_r.next(d3).value
}).then(data=>{
    console.log(data) // hello
})
Copy the code

It.next ().value returns a promise, uses the then method, takes its successful callback value, and passes in the next next.

This will get us what we want, but it’s too much trouble. Hence the combination of generator+ CO!

Installation:

$ npm install co
Copy the code

Use:

co(r()).then(data=> {
    console.log(data)
})
Copy the code

Co iterates through the it.next() method until done has a Boolean value of true and returns the result of the generator.

The roughly executed code is as follows:

function co(it) {
    return new Promise((resolve, reject)=> {
        function next(data) {
            let {value, done} = it.next(data)
            if(done){
                resolve(value)
            }else{
                value.then(data=> {
                    next(data)
                },reject)
            }
        }
        next()
    })
}
Copy the code

Five, the async + await

Async functions are syntactic sugar for Generator functions.

Easier to use than Generator functions

  1. You can make your code look like synchronization
  2. You can try + catch
  3. You can use the Promise API
async function r() {
   try{
        let r1 = await read('1.txt','utf8')
        let r2 = await read(r1,'utf8')
        let r3 = await read(r2,'utf8')
        return r3
    }catch(e){
        console.log('e',e)
    }
}

r().then(data=> {
    console.log(data)
},err=>{
    console.log('err',err)
})
Copy the code

The async function returns a Promise object, and callbacks can be added using the then method. It returns “await” and waits for the function to execute.

reference

  • Use Bluebird for more powerful Promise

  • [The meaning and usage of Generator functions

] (www.ruanyifeng.com/blog/2015/0)…

Recruitment is stuck

Bytedance is hiring!

Position Description: Front-end Development (senior) ToB Direction — Video Cloud (Base: Shanghai, Beijing)

1. Responsible for the productization of multimedia services such as voD/live broadcast/real-time communication and the construction of business cloud platform;

2. Responsible for the construction and system development of multimedia quality system, operation and maintenance system;

3, good at abstract design, engineering thinking, focus on interaction, to create the ultimate user experience.

Job requirements

1. Major in Computer, communication and electronic information science is preferred;

2, familiar with all kinds of front end technology, including HTML/CSS/JavaScript/Node. Js, etc.

3. In-depth knowledge of JavaScript language and use of React or vue.js and other mainstream development frameworks;

4. Familiar with Node.js, Express/KOA and other frameworks, experience in developing large server programs is preferred;

5. Have some understanding of user experience, interactive operation and user demand analysis, experience in product or interface design is preferred;

6, own technical products, open source works or active open source community contributors are preferred.

Position window

Relying on the audio and video technology accumulation and basic resources of douyin, Watermelon video and other products, the video Cloud team provides customers with the ultimate one-stop audio and video multimedia services, including audio and video on demand, live broadcasting, real-time communication, picture processing, etc. Internally as the video technology center, service internal business; External to create productized audio and video multimedia service solutions, serving enterprise users.

The team has standardized project iteration process and perfect project role configuration; Strong technical atmosphere, embrace open source community, regular sharing, so that everyone can grow with the rapid business, with technology to change the world!

The delivery way

You can send your resume directly to: [email protected]

You can also scan the push two-dimensional code online delivery, looking forward to your participation! ~